繁体   English   中英

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

[英]How to reduce context switches when traversing a Seq of Scala futures

我有一个程序需要获取大量(~300)条记录并对它们进行一些操作。 记录被缓存,因此它不占用 CPU,几乎不占用任何时间。 但是,当 CPU 利用率为 50% 时,p95 延迟为 40 毫秒。 查看堆栈跟踪时,线程大部分时间都处于 Parked 状态,最常用的方法是“java.util.concurrent.locks.LockSupport.parkNanos(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 调用都从缓存中提供服务,我该怎么办(我可以预先警告缓存,并使用后台刷新 )

ps堆转储: https : //heaphero.io/my-heap-report.jsp? p = YXJjaGl2ZWQvMjAyMC8wMi8xOC8tLWJhc2UtZm9yay56aXAtNC0yNi02Lmpzb24tLQ==

甲巧妙的方法,以避免上下文切换使用的Scala当Future S包括在使用parasitic作为ExecutionContext通过具有其的Runnable在其上调用执行的线程中运行,然后毕竟屈服背控制给调用者,从其他线程其中“窃取执行时间其Runnable 已被执行”。 parasitic从 Scala 2.13 开始可用,但您可以通过查看其代码(此处为 2.13.1 版)轻松理解它并将其移植到 2.13 之前的项目。 对于 2.13 之前的项目,一个简单但有效的实现将简单地运行Runnable而无需在线程上分派它们,这可以解决问题,如下面的代码片段所示:

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 = ???

}

parasitic实现当然更加微妙。 为了更深入地了解推理和有关其使用的一些注意事项,我建议您将引入的parasitic引用为公开可用的 API(它已经实现但保留供内部使用)。

引用原始 PR 描述:

一个同步的、蹦床的、ExecutionContext 已经在 Future 实现中使用了很长时间,以尽可能便宜地运行受控逻辑。

我相信在很多用例中,为了效率,以安全(-ish)方式同步执行逻辑而无需用户自己实现该 ExecutionContext 的逻辑是有意义的 - 实现到至少可以说。

重要的是要记住 ExecutionContext 应该通过隐式参数提供,以便调用者可以决定应该在哪里执行逻辑。 ExecutionContext.parasitic 的使用意味着逻辑可能最终运行在非设计或不打算运行指定逻辑的线程/池上。 例如,您可能最终在 IO 设计的池上运行受 CPU 限制的逻辑,反之亦然。 因此,只有在真正有意义时才建议使用寄生参数。 对于某些嵌套调用模式,也存在遇到 StackOverflowErrors 的真正风险,其中深度调用链在寄生执行器中结束,导致在后续执行中使用更多堆栈。 目前,寄生 ExecutionContext 将允许最多 16 个嵌套调用序列,如果发现它会导致问题,这可能会在未来更改。

正如parasitic的官方文档中所建议的那样,建议您仅在执行的代码快速将控制权返回给调用者时才使用它。 以下是 2.13.1 版引用的文档:

警告:只执行能够快速将控制权返回给调用者的逻辑。

此 ExecutionContext 通过让其 Runnable 在调用 execute 的线程上运行,然后在其所有Runnable 执行完毕后将控制权交还给调用者,从而从其他线程中窃取执行时间。 嵌套的 execute 调用将被取消,以防止不受控制的堆栈空间增长。

当使用带有抽象(例如 Future)的寄生时,在许多情况下,对于哪个线程将执行逻辑是不确定的,因为它取决于 Future 何时/是否完成。

不要叫提交给本执行上下文中的Runnable任何阻塞的代码,因为它会阻止其他排队的Runnable,并调用线程的进展。

滥用此 ExecutionContext 的症状包括但不限于死锁和严重的性能问题。

任何 NonFatal 或 InterruptedExceptions 都将报告给 defaultReporter。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM