如何在遍历Scala期货序列时减少上下文切换
我有一个程序,需要获取大量(~300)条记录并对它们执行一些操作。记录是缓存的,因此不需要CPU,几乎不需要时间。然而,在CPU使用率为50%时,p95延迟为40ms。查看堆栈跟踪时,线程大部分时间都处于停止状态,最常用的方法是“java.util.concurrent.locks.LockSupport.parknos(LockSupport.java:215)”。所以我猜大部分时间都用于上下文切换如何在遍历Scala期货序列时减少上下文切换,scala,akka,threadpool,forkjoinpool,Scala,Akka,Threadpool,Forkjoinpool,我有一个程序,需要获取大量(~300)条记录并对它们执行一些操作。记录是缓存的,因此不需要CPU,几乎不需要时间。然而,在CPU使用率为50%时,p95延迟为40ms。查看堆栈跟踪时,线程大部分时间都处于停止状态,最常用的方法是“java.util.concurrent.locks.LockSupport.parknos(LockSupport.java:215)”。所以我猜大部分时间都用于上下文切换 def getObject(id: Ing): Future[SomeObject] = ?
def getObject(id: Ing): Future[SomeObject] = ??? // Can make a call to DB, but usually cached
val ids: Seq[Int] = ??? // 300 ints
Future.iterate(ids)(getObject)
如何提高性能?我尝试过Akka FastFuture,并尝试使用全局(工作窃取线程池)和Akka默认调度程序(ForkJoin)
具体地
1) 如果假设某些getObject调用阻塞(缓存未命中),我该怎么办
2) 如果我们假设所有getObject调用都是从缓存中提供的,我该怎么办(我可以对缓存进行预热,并使用后台刷新)
p、
堆转储:最好的解决方案是更改数据库访问,以便在一个
将来的中获取多个对象。即使对可以获取的对象数量有限制,它仍将大大减少开销。在使用ScalaFuture
s时,避免上下文切换的一个巧妙技巧是使用寄生作为执行上下文,这是“通过在调用execute的线程上运行其Runnable,然后在其所有Runnable都已执行后将控制权交还给调用者,从其他线程窃取执行时间“寄生
从Scala 2.13开始就可以使用,但是您可以通过查看。对于2.13版本之前的项目,一个简单但有效的实现只需运行Runnable
s,而不需要在线程上分派它们,这就完成了任务,如下面的代码片段所示:
object parasitic212 extends ExecutionContext {
override def execute(runnable: Runnable): Unit =
runnable.run()
// reporting failures is left as an exercise for the reader
override def reportFailure(cause: Throwable): Unit = ???
}
当然,寄生的
实现更加细致。为了更深入地了解其原因和一些使用注意事项,我建议您将其称为公开可用的API(它已经实现,但保留供内部使用)
引用原始PR描述:
在未来的实现中,一个同步的、蹦床式的ExecutionContext已经使用了很长一段时间,以尽可能便宜地运行受控逻辑
我相信有很多用例,为了提高效率,以安全(-ish)的方式同步执行逻辑是有意义的,而不需要用户自己为该ExecutionContext实现逻辑——至少可以说,实现起来很困难
记住ExecutionContext应该通过一个隐式参数提供,这一点很重要,这样调用者就可以决定在何处执行逻辑。使用ExecutionContext.Paratical意味着逻辑可能最终运行在未设计或未打算运行指定逻辑的线程/池上。例如,您可能会在IO设计的池上运行CPU绑定逻辑,反之亦然。因此,只有在真正有意义的情况下才建议使用寄生虫。对于某些嵌套调用模式,如果深度调用链最终出现在寄生执行器中,则也存在命中StackOverflowers错误的实际风险,从而导致在后续执行中使用更多堆栈。当前,寄生ExecutionContext将允许嵌套的调用序列,最大值为16,如果发现它会导致问题,则将来可能会更改
正如中所建议的,建议您仅在执行的代码将控制权快速返回给调用方时使用此选项。以下是2.13.1版引用的文件:
警告:仅执行将快速将控制权返回给调用方的逻辑
此ExecutionContext通过让其可运行程序在调用execute的线程上运行,然后在其所有可运行程序都已执行后将控制权交还给调用者,从而从其他线程窃取执行时间。execute的嵌套调用将被践踏,以防止不受控制的堆栈空间增长
当与诸如Future之类的抽象一起使用寄生时,在许多情况下,哪个线程将执行逻辑是不确定的,因为这取决于未来何时/是否完成
不要在提交到此ExecutionContext的Runnables中调用任何阻塞代码,因为这将阻止其他排队的Runnables和调用线程的进程
误用此ExecutionContext的症状包括但不限于死锁和严重的性能问题
任何非致命或中断异常都将报告给defaultReporter
您能提供一些有关您正在使用的缓存的详细信息吗?我怀疑它上面的操作(例如get或update)可能在内部阻塞。我猜测您使用的是基于ForkJoinPool的ExecutionContext,这是一个CPU渴望池。如果您通常没有任何其他负载,那么最好使用其他ExecutionContext。或者只是将其并行度设置为一个较低的数字。