我遇到的是一组在内存中的数据执行一些高度并行处理的.NET应用程序一个奇怪的行为。
当在多核心处理器(IntelCore2四Q6600的2.4GHz)运行它表现出非线性缩放作为多个线程踢掉来处理数据。
当作为在单个核心上的非多线程循环中运行,该方法能够完成大约每秒240万计算。 当四个线程运行你所期望的四倍的吞吐量 - 在每秒900万次计算,附近的地方 - 但很可惜,没有。 在实践中,只完成了约每秒4.1百万......从预期吞吐量相当有点短。
此外,该行为时,不管自己是否使用PLINQ,线程池,或四个明确创建线程。 很奇怪...
没有其他的计算机上运行使用的CPU时间,也没有参与任何计算锁或其他同步对象...这应该只是通过数据撕毁领先。 我已经证实了这一点(尽可能)通过perfmon的数据看,而进程运行...并没有报道线程争或垃圾收集活动。
我当时的理论:
- 所有的技术(线程上下文切换等)的开销压倒的计算
- 线程是没有得到分配到四个内核,并花一些时间等待同一个处理器内核..不知道如何测试这个理论...
- .NET CLR线程没有在预期的优先级运行还是有一些隐藏的内部开销。
下面是应该表现出同样的行为的代码有代表性的摘录:
var evaluator = new LookupBasedEvaluator();
// find all ten-vertex polygons that are a subset of the set of points
var ssg = new SubsetGenerator<PolygonData>(Points.All, 10);
const int TEST_SIZE = 10000000; // evaluate the first 10 million records
// materialize the data into memory...
var polygons = ssg.AsParallel()
.Take(TEST_SIZE)
.Cast<PolygonData>()
.ToArray();
var sw1 = Stopwatch.StartNew();
// for loop completes in about 4.02 seconds... ~ 2.483 million/sec
foreach( var polygon in polygons )
evaluator.Evaluate(polygon);
s1.Stop();
Console.WriteLine( "Linear, single core loop: {0}", s1.ElapsedMilliseconds );
// now attempt the same thing in parallel using Parallel.ForEach...
// MS documentation indicates this internally uses a worker thread pool
// completes in 2.61 seconds ... or ~ 3.831 million/sec
var sw2 = Stopwatch.StartNew();
Parallel.ForEach(polygons, p => evaluator.Evaluate(p));
sw2.Stop();
Console.WriteLine( "Parallel.ForEach() loop: {0}", s2.ElapsedMilliseconds );
// now using PLINQ, er get slightly better results, but not by much
// completes in 2.21 seconds ... or ~ 4.524 million/second
var sw3 = Stopwatch.StartNew();
polygons.AsParallel(Environment.ProcessorCount)
.AsUnordered() // no sure this is necessary...
.ForAll( h => evalautor.Evaluate(h) );
sw3.Stop();
Console.WriteLine( "PLINQ.AsParallel.ForAll: {0}", s3.EllapsedMilliseconds );
// now using four explicit threads:
// best, still short of expectations at 1.99 seconds = ~ 5 million/sec
ParameterizedThreadStart tsd = delegate(object pset) { foreach (var p in (IEnumerable<Card[]>) pset) evaluator.Evaluate(p); };
var t1 = new Thread(tsd);
var t2 = new Thread(tsd);
var t3 = new Thread(tsd);
var t4 = new Thread(tsd);
var sw4 = Stopwatch.StartNew();
t1.Start(hands);
t2.Start(hands);
t3.Start(hands);
t4.Start(hands);
t1.Join();
t2.Join();
t3.Join();
t4.Join();
sw.Stop();
Console.WriteLine( "Four Explicit Threads: {0}", s4.EllapsedMilliseconds );