Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
{
E e = ((C)(x)).GetEnumerator();
try {
while (e.MoveNext()) {
V v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
}{
E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
}Во-первых, это breaking changes
раньше оно работало так же как и for
Also, it seems reasonable that the user of the foreach loop might think of there being a «fresh» loop variable every time, not just a fresh value in the same old variable. Since the foreach loop variable is not mutable by user code, this reinforces the idea that it is a succession of values, one per loop iteration, and not «really» the same variable over and over again.
static void TestArrayFor()
{
long totalValue = 0;
for (int i = 0; i < g_IntArray.Length; i++)
totalValue += i;
}
static void TestListFor()
{
long totalValue = 0;
for (int i = 0; i < g_IntList.Count; i++)
totalValue += i;
}
Как видно, для небольших списков лучше всего использовать стандартный цикл с внешним счётчиком. По производительности ForEach проигрывает ему в разы.
Поэтому основной вывод статьи мягко выражаясь, некорректен. Не в разы.
for естественно будет работать быстрее, чем List.ForEach — но для того, чтобы это доказать, не нужно ничего тестировать и рисовать графики. Достаточно просто заглянуть в исходники List.ForEach.
На вашем скрипте ситуация совсем не та, которую тестирует автор оригинальной статьи — вы тестируете расходы на сам факт запуска цикла. Автор оригинала тестировал скорость перебора элементов. Это две совсем разные метрики (и сняты они на разных платформах).
List.ForEach быстрее, чем foreach — очень спорное утверждение. Разница между 381ms и 401ms на 100КК итераций, на практически пустом цикле, настолько незначительна, что при разработке можно спокойно выбирать то, что подходит по семантике — на производительность это не повлияет вообще никак. Практически, разницы нет. А если и есть — то она может качнуться в другую сторону на те же 20ms в зависимости от фазы луны.
Он будет вызывать callvirt IL инструкцию, но два вызова медленнее, чем один callvirt. Итак, я ожидаю, что List<T>.ForEach на самом деле будет быстрее.Практика подтверждает теорию, а не наоборот. На практике, же согласно оригинальной статье, for без оптимизации компилятора работает медленнее List.ForEach.
Честно неуловил, в чём вы нашли отличие цикла от перебора?
В чём же его спорность
Цикл for (который написан внутри List.ForEach) находится в mscorlib.dll. Которая, естественно, скопилирована с оптимизацией, и для которой заранее сгерерирован не-отладочный native image. Т.е. тест «без оптимизации» на самом деле сравнивает «for без оптимизации и без bounds check elimination» и «for c вызовом action c оптимизацией». Последний, естественно, оказывается быстрее. Так что практика вполне подтверждает теорию.
Автор вызывает цикл один раз (и один раз тратит время на создание энумератора, поэтому цена создания энумератора вообще никак не влияет на его результаты). Вы вызываете цикл 1000 раз (или больше), 1000 раз тратите время на создание энумератора, да еще и измеряете затраты на внешний цикл и switch (насколько я понял). Поэтому у вас в левой стороне графика такие сильные расхождения.
Спорность в том, что сравнивать только средние значения некорректно. Нужно учитывать разброс результатов.
Вот результаты с моей машины (с) на 20 попытках:
ForEach: среднее 367.9, отклонение (оценочное) 13.7. 95% попыток будут в диапазоне 354-381.
foreach: среднее 373.8, отклонение (оценочное) 16.8. 95% попыток будут в диапазоне 356-390.
Средние рядом, разброс значений гораздо больше чем разница средних.
С достаточно большой вероятностью конкретный запуск foreach где-то в приложении будет быстрее (не намного, но быстрее) конкретного запуска ForEach.
В моей выборке есть пара значений с разницей в 39ms (10%!) в пользу foreach. Можно это засчитать как аргумент в пользу спорности?
static long TestListFor()
{
Debugger.Launch();
long totalValue = 0;
for (int i = 0; i < g_IntList.Count; i++)
{
totalValue += g_IntList[i];
}
return totalValue;
}
.method private hidebysig static int64 TestListFor() cil managed
{
// Code size 47 (0x2f)
.maxstack 3
.locals init ([0] int64 totalValue,
[1] int32 i)
IL_0000: call bool [mscorlib]System.Diagnostics.Debugger::Launch()
IL_0005: pop
IL_0006: ldc.i4.0
IL_0007: conv.i8
IL_0008: stloc.0
IL_0009: ldc.i4.0
IL_000a: stloc.1
IL_000b: br.s IL_0020
IL_000d: ldloc.0
IL_000e: ldsfld class [mscorlib]System.Collections.Generic.List`1<int32> ForEachTest.Program::g_IntList
IL_0013: ldloc.1
IL_0014: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<int32>::get_Item(int32)
IL_0019: conv.i8
IL_001a: add
IL_001b: stloc.0
IL_001c: ldloc.1
IL_001d: ldc.i4.1
IL_001e: add
IL_001f: stloc.1
IL_0020: ldloc.1
IL_0021: ldsfld class [mscorlib]System.Collections.Generic.List`1<int32> ForEachTest.Program::g_IntList
IL_0026: callvirt instance int32 class [mscorlib]System.Collections.Generic.List`1<int32>::get_Count()
IL_002b: blt.s IL_000d
IL_002d: ldloc.0
IL_002e: ret
} // end of method Program::TestListFor
; totalValue = 0
00007FFA38F30441 xor ecx,ecx
; for
; проверка i < list.Count и границ внутреннего массива
00007FFA38F30443 mov r9,0BF57335778h
00007FFA38F3044D mov r9,qword ptr [r9]
00007FFA38F30450 mov eax,dword ptr [r9+18h]
00007FFA38F30454 cmp edx,eax
00007FFA38F30456 jl 00007FFA38F30460
00007FFA38F30458 mov rax,rcx
00007FFA38F3045B jmp 00007FFA38F30486
00007FFA38F3045D nop dword ptr [rax]
00007FFA38F30460 cmp edx,eax
00007FFA38F30462 jae 00007FFA38F30495
00007FFA38F30464 mov r9,qword ptr [r9+8]
00007FFA38F30468 movsxd r10,r8d
00007FFA38F3046B mov rax,qword ptr [r9+8]
00007FFA38F3046F cmp r10,rax
00007FFA38F30472 jae 00007FFA38F30490
; загрузка i-го элемента в eax, в IL выглядело как callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1::get_Item(int32)
00007FFA38F30474 mov eax,dword ptr [r9+r10*4+10h]
00007FFA38F30479 movsxd rax,eax
; totalValue += rax
00007FFA38F3047C add rcx,rax
00007FFA38F3047F inc edx
00007FFA38F30481 inc r8d
; jump на начало for
00007FFA38F30484 jmp 00007FFA38F30443
00007FFA38F54014 mov qword ptr [rsp+20h],0
00007FFA38F5401D mov dword ptr [rsp+28h],0
00007FFA38F54025 jmp 00007FFA38F54079
00007FFA38F54027 mov rcx,0B42D95E728h
00007FFA38F54031 mov rcx,qword ptr [rcx]
00007FFA38F54034 mov rax,qword ptr [rsp+20h]
00007FFA38F54039 mov qword ptr [rsp+30h],rax
00007FFA38F5403E mov qword ptr [rsp+38h],rcx
00007FFA38F54043 mov rax,qword ptr [rsp+38h]
00007FFA38F54048 cmp byte ptr [rax],0
00007FFA38F5404B mov rcx,qword ptr [rsp+38h]
00007FFA38F54050 mov edx,dword ptr [rsp+28h]
; вызов get_Item(int32)
00007FFA38F54054 call 00007FFA9743F430
00007FFA38F54059 mov dword ptr [rsp+40h],eax
00007FFA38F5405D movsxd rcx,dword ptr [rsp+40h]
00007FFA38F54062 mov rax,qword ptr [rsp+30h]
; totalValue += element
00007FFA38F54067 add rax,rcx
00007FFA38F5406A mov qword ptr [rsp+20h],rax
00007FFA38F5406F mov eax,dword ptr [rsp+28h]
00007FFA38F54073 inc eax
00007FFA38F54075 mov dword ptr [rsp+28h],eax
00007FFA38F54079 mov rcx,0B42D95E728h
00007FFA38F54083 mov rcx,qword ptr [rcx]
00007FFA38F54086 mov eax,dword ptr [rsp+28h]
00007FFA38F5408A mov dword ptr [rsp+44h],eax
00007FFA38F5408E mov qword ptr [rsp+48h],rcx
00007FFA38F54093 mov rax,qword ptr [rsp+48h]
00007FFA38F54098 cmp byte ptr [rax],0
00007FFA38F5409B mov rcx,qword ptr [rsp+48h]
; вызов Count и проверка i < Count
00007FFA38F540A0 call 00007FFA9743F470
00007FFA38F540A5 mov dword ptr [rsp+50h],eax
00007FFA38F540A9 mov eax,dword ptr [rsp+50h]
00007FFA38F540AD cmp dword ptr [rsp+44h],eax
00007FFA38F540B1 jl 00007FFA38F54027
Initializing 100,000,000 items…
Testing for on int[]…
Executed in 0.275374 seconds.
Testing foreach on int[]…
Executed in 0.485148 seconds.
Testing Array.ForEach…
Executed in 0.528751 seconds.
Testing for on List…
Executed in 0.569238 seconds.
Testing foreach on List…
Executed in 0.548733 seconds.
Testing List.ForEach…
Executed in 0.534959 seconds.
static void TestArrayFor()
{
long totalValue = 0;
for (int i = 0; i < g_IntArray.Length; i++)
totalValue += g_IntArray[i];
}
static void TestListFor()
{
long totalValue = 0;
for (int i = 0; i < g_IntList.Count; i++)
totalValue += g_IntArray[i];
}

private static void RunTest(string header, TestMethod testMethod)
{
HighResolutionTimer timer = new HighResolutionTimer();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine(header);
timer.Start();
testMethod();
timer.Stop();
Console.WriteLine("Executed in {0}.", timer);
Console.WriteLine();
}
Что быстрее? foreach vs. List.ForEach vs. for-loop