Многопоточность с IEnumerables , которые оцениваются несколько раз параллельно и являются дорогостоящими для оценки, не использует 100% CPU . Примером является функция Aggregate () в сочетании с Concat ():
// Initialisation.
// Each IEnumerable<string> is made so that it takes time to evaluate it
// everytime when it is accessed.
IEnumerable<string>[] iEnumerablesArray = ...
// The line of the question (using less than 100% CPU):
Parallel.For(0, 1000000, _ => iEnumerablesArray.Aggregate(Enumerable.Concat).ToList());
Вопрос: Почему параллельный код, в котором IEnumerables несколько раз параллельно оцениваются, не использует 100% ЦП? Код не использует блокировки или ожидает, поэтому это поведение является неожиданным. Полный код для симуляции это в конце поста.
Примечания и правки:
- Интересный факт: Если код
Enumerable.Range(0, 1).Select(__ => GenerateLongString())
полного кода в конце изменяется на
Enumerable.Range(0, 1).Select(__ => GenerateLongString()).ToArray().AsEnumerable()
,
, затем инициализация занимает секунды, а после этого процессор используется до 100% (проблем не возникает)
- Интересный факт2: (из комментария) Когда метод
GenerateLongString()
становится менее тяжелым для GC и более интенсивным для CPU, тогда CPU переходит на 100%. Так что причина связана с реализацией этого метода. Но, что интересно, если текущая форма GenerateLongString()
вызывается без IEnumerable, CPU также переходит на 100%:
Parallel.For(0, int.MaxValue, _ => GenerateLongString());
Так что тяжесть GenerateLongString()
здесь не единственная проблема.
- Факт 3: (из комментария) Предлагаемый Параллельный визуализатор показал, что потоки проводят большую часть своего времени в сети
clr.dll!WKS::gc_heap::wait_for_gc_done
,
в ожидании завершения GC. Это происходит внутри string.Concat()
из GenerateLongString()
.
- То же поведение наблюдается при ручном запуске нескольких Task.Factory.StartNew () или Thread.Start ()
- Такое же поведение наблюдается в Win 10 и Windows Server 2012
- Такое же поведение наблюдается на реальной машине и виртуальной машине
- Релиз против отладки не имеет значения.
- .Net версия протестирована: 4.7.2
Полный код:
class Program
{
const int DATA_SIZE = 10000;
const int IENUMERABLE_COUNT = 10000;
static void Main(string[] args)
{
// initialisation - takes milliseconds
IEnumerable<string>[] iEnumerablesArray = GenerateArrayOfIEnumerables();
Console.WriteLine("Initialized");
List<string> result = null;
// =================
// THE PROBLEM LINE:
// =================
// CPU usage of next line:
// - 40 % on 4 virtual cores processor (2 physical)
// - 10 - 15 % on 12 virtual cores processor
Parallel.For(
0,
int.MaxValue,
(i) => result = iEnumerablesArray.Aggregate(Enumerable.Concat).ToList());
// just to be sure that Release mode would not omit some lines:
Console.WriteLine(result);
}
static IEnumerable<string>[] GenerateArrayOfIEnumerables()
{
return Enumerable
.Range(0, IENUMERABLE_COUNT)
.Select(_ => Enumerable.Range(0, 1).Select(__ => GenerateLongString()))
.ToArray();
}
static string GenerateLongString()
{
return string.Concat(Enumerable.Range(0, DATA_SIZE).Select(_ => "string_part"));
}
}