我有一个大型数组,需要对该数组的每个元素进行繁重的CPU工作。
基于my similar question,Alexei Kaigorodov先生建议最好的方法是在每个单独的线程上拆分计算的每个数据块。
我使用Kotlin协程实现算法:
suspend fun predictAll(movingVehicles: List<MovingVehicle>): List<MovingVehicle?> {
val prevTime = Timestamp(Date().time)
val nextTime = Timestamp(Date().time)
val ctx = Dispatchers.Default
val processors = Runtime.getRuntime().availableProcessors()
val chunks = movingVehicles.chunked(movingVehicles.count() / processors)
val s = coroutineScope {
val res = mutableListOf<Deferred<List<MovingVehicle?>>>()
for (c in chunks) {
val r = async(ctx) {
c.map { predictLocation(it, prevTime, nextTime) }
}
res.add(r)
}
res.awaitAll()
}
return s.flatten()
}
private fun predictLocation(
mv: MovingVehicle,
prevTime: Timestamp,
nextTime: Timestamp,
relevance: Int = 5
): MovingVehicle?
它有效,但是也许有更好的方法吗? 我在寻找ExecutorService,但看起来它比协程需要更多样板代码。
答案 0 :(得分:2)
这实际上是使用协程的Kotilinic方法。您提交可以同时执行的异步任务,然后等待它们完成。
令人深思。一切都在线程中执行。这意味着协程也将在线程上执行,如果您的任务正在阻塞该线程将被阻塞。协程将不会保存在那里。因此,通常最好的做法是创建一个Threadpool
,并且具有最适合应用程序的属性(背压机制,最小/最大线程数等)
现在,在您的情况下,您有cpu绑定的任务,通过拥有大量线程无法获得更高的性能。对于此类任务,Amdahl's_law的实际应用给出-
#threads = #cpu-cores - 1
默认情况下,协程由common pool支持,该线程与上面提到的线程数相同,因此保留默认设置似乎很好。
但是,可能有多个库正在使用该池,并且如果其中任何一个都具有IO阻止任务,则会降低性能。我建议您创建自己的ForkJoinPool并将其用作调度程序
val nOfThreads = Runtime.getRuntime().availableProcessors() - 1;
val ctx = ForkJoinPool( if (nOfThreads == 0) then 1 else nOfThreads).asCoroutineDispatcher()