Я столкнулся со странным поведением в приложении .NET, которое выполняет некоторую высокопараллельную обработку набора данных в памяти.
При работе на многоядерном процессоре (IntelCore2 Quad Q6600 2,4 ГГц) он демонстрирует нелинейное масштабирование, поскольку для обработки данных запускается несколько потоков.
При запуске в виде не многопоточного цикла на одном ядре процесс может выполнять примерно 2,4 миллиона вычислений в секунду. При работе с четырьмя потоками вы ожидаете пропускной способности в четыре раза больше - где-то около 9 миллионов вычислений в секунду - но, увы, нет. На практике это всего лишь около 4,1 миллиона в секунду ... совсем немного от ожидаемой пропускной способности.
Кроме того, поведение происходит независимо от того, использую ли я PLINQ, пул потоков или четыре явно созданных потока. Довольно странно ...
Больше ничего не выполняется на машине, использующей процессорное время, и при этом нет никаких блокировок или других объектов синхронизации, участвующих в вычислении ... это должно просто прорвать данные вперед. Я подтвердил это (насколько это возможно), просматривая данные 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 );