我使用了StriplingWarror的测试来查找差异的来源。我之所以这样做,是因为当我用Reflector查看代码时,Parallel类与创建一堆任务并让它们运行没有任何区别。
从理论上讲,这两种方法在运行时间上应该是等效的。但是,(不太现实的)空动作测试表明,Parallel类要快得多。
任务版本几乎花费所有时间都在创建新任务,这会导致许多垃圾收集。你看到的速度差异纯粹是由于你创建了许多任务,这些任务很快就变成了垃圾。
相比之下,Parallel类会创建自己的派生任务类,在所有CPU上并发运行。在所有核心上只有一个物理任务正在运行。现在同步是在任务委托内部发生的,这解释了Parallel类速度更快的原因。
ParallelForReplicatingTask task2 = new ParallelForReplicatingTask(parallelOptions, delegate {
for (int k = Interlocked.Increment(ref actionIndex); k <= actionsCopy.Length; k = Interlocked.Increment(ref actionIndex))
{
actionsCopy[k - 1]();
}
}, TaskCreationOptions.None, InternalTaskOptions.SelfReplicating);
task2.RunSynchronously(parallelOptions.EffectiveTaskScheduler);
task2.Wait();
那么什么比这更好呢?最好的任务是从未运行的任务。如果你需要创建许多任务,以至于它们成为垃圾回收器的负担,那么你应该远离任务API并坚持使用Parallel类,它可以直接在所有核心上进行并行执行而不创建新任务。
如果你需要变得更快,可能手动创建线程并使用手动优化的数据结构,以给你最大的速度和访问模式的性能解决方案是最高效的。但是由于TPL和Parallel API已经被大量调整过了,你成功做到这一点的可能性很小。通常,你需要使用其中的一个重载来配置你的运行任务或Parallel类,以用更少的代码实现相同的功能。
但是,如果你有非标准的线程模式,可能最好不要使用TPL,以充分利用你的核心。即使Stephen Toub也提到,TPL API并不是为了超高的性能而设计的,而主要目的是为“普通”程序员让线程更容易。要在特定情况下击败TPL,你需要非常高超的水平,需要了解CPU缓存行、线程调度、内存模型、JIT代码生成等方面的知识,在你的具体场景中提出更好的解决方案。