一个不太令人满意的替代方案是阻止每次比较,直到评估未来。如果评估排序谓词的成本很高,那么排序将花费很长时间。实际上,这只是将可能并发的程序转换为顺序程序;使用期货的所有好处都将丧失。
import scala.concurrent.duration._
implicit val executionContext = ExecutionContext.Implicits.global
val sortingPredicate: (Int, Int) => Future[Boolean] = (a, b) => Future{
Thread.sleep(20) // Assume this is a costly comparison
a < b
}
val unsorted = List(4, 2, 1, 5, 7, 3, 6, 8, 3, 12, 1, 3, 2, 1)
val sorted = unsorted.sortWith((a, b) =>
Await.result(sortingPredicate(a, b), 5000.millis) // careful: May throw an exception
)
println(sorted) // List(1, 1, 1, 2, 2, 3, 3, 3, 4, 5, 6, 7, 8, 12)
我不知道是否有使用异步比较的开箱即用解决方案。但是,您可以尝试实现自己的排序算法。如果我们考虑Quicksort,它平均运行在O(n log(n)),那么我们实际上可以很容易地利用异步比较。
如果你不熟悉快速排序,算法基本如下
- 从集合中选择一个元素(称为Pivot)
- 将枢轴与所有剩余元素进行比较。创建一个包含小于枢轴的元素和一个包含大于枢轴的元素的集合。
- 对两个新集合进行排序并将它们连接起来,将枢轴放在中间。
由于第 2 步执行了大量独立比较,我们可以同时评估这些比较。
这是一个未优化的实现:
object ParallelSort {
val timeout = Duration.Inf
implicit class QuickSort[U](elements: Seq[U]) {
private def choosePivot: (U, Seq[U]) = elements.head -> elements.tail
def sortParallelWith(predicate: (U, U) => Future[Boolean]): Seq[U] =
if (elements.isEmpty || elements.size == 1) elements
else if (elements.size == 2) {
if (Await.result(predicate(elements.head, elements.tail.head), timeout)) elements else elements.reverse
}
else {
val (pivot, other) = choosePivot
val ordering: Seq[(Future[Boolean], U)] = other map { element => predicate(element, pivot) -> element }
// This is where we utilize asynchronous evaluation of the sorting predicate
val (left, right) = ordering.partition { case (lessThanPivot, _) => Await.result(lessThanPivot, timeout) }
val leftSorted = left.map(_._2).sortParallelWith(predicate)
val rightSorted = right.map(_._2).sortParallelWith(predicate)
leftSorted ++ (pivot +: rightSorted)
}
}
}
可以使用(与上面相同的例子)如下:
import ParallelSort.QuickSort
val sorted2 = unsorted.sortParallelWith(sortingPredicate)
println(sorted2) // List(1, 1, 1, 2, 2, 3, 3, 3, 4, 5, 6, 7, 8, 12)
请注意,这种快速排序的实现是否比完全顺序的内置排序算法更快或更慢在很大程度上取决于比较的成本:比较必须阻塞的时间越长,上述替代解决方案就越差.在我的机器上,考虑到代价高昂的比较(20 毫秒)和上面的列表,内置排序算法运行时间约为 1200 毫秒,而此自定义快速排序运行时间约为 200 毫秒。如果你担心性能,你可能想想出更聪明的东西。 编辑:我刚刚检查了内置排序算法和自定义快速排序算法执行了多少比较:显然,对于给定列表(以及我随机输入的一些其他列表)内置算法使用更多的比较,因此并行执行带来的性能改进可能不是那么好。我不知道更大的列表,但无论如何您都必须根据您的特定数据对它们进行分析。