如何在遍历Scala期货序列时减少上下文切换

如何在遍历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] = ?

我有一个程序,需要获取大量(~300)条记录并对它们执行一些操作。记录是缓存的,因此不需要CPU,几乎不需要时间。然而,在CPU使用率为50%时,p95延迟为40ms。查看堆栈跟踪时,线程大部分时间都处于停止状态,最常用的方法是“java.util.concurrent.locks.LockSupport.parknos(LockSupport.java:215)”。所以我猜大部分时间都用于上下文切换

 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、
堆转储:

最好的解决方案是更改数据库访问,以便在一个
将来的
中获取多个对象。即使对可以获取的对象数量有限制,它仍将大大减少开销。

在使用Scala
Future
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。或者只是将其并行度设置为一个较低的数字。