Комментарии 23
Проводил эксперименты на x64 шестом дотнете, все ссылки с шарплаба тоже настроены x64, хоть там пятый, разницы это не дало (в коде бенчмарка я закомментил аттрибут DisassemblyDiagnoser, с которым вы можете посмотреть именно тот асм, который гоняет бенчмарк)
Кстати, интересно то, что если поставить Default на шарплабе, т. е. 32-битный рантайм, то мы в последнем эксперименте получили бы:
L0007: add eax, edx
L0009: mov edx, [ebp+0x10]
L000c: add edx, [ebp+0xc]
L000f: mov [ebp+0x10], edx
L0012: cmp edx, [ebp+8]
L0015: jne short L0007
Так что... используйте 64 бита! :)
А еще можно попробовать включить Dynamic PGO, запустив бенчмарк так:
dotnet run -c release --envVars DOTNET_TieredPGO:1 DOTNET_ReadyToRun:0 DOTNET_TC_QuickJitForLoops:1
Но у меня получились те же результаты, что и в статье, так что я решил оставить это в комментарии.
Странно. У меня получилось немного по-другому:
BenchmarkDotNet=v0.13.1, OS=arch
Intel Core i7-2600K CPU 3.40GHz (Sandy Bridge), 1 CPU, 4 logical and 4 physical cores
.NET SDK=6.0.100-preview.7.21379.14
[Host] : .NET 6.0.0 (6.0.21.37719), X64 RyuJIT
DefaultJob : .NET 6.0.0 (6.0.21.37719), X64 RyuJIT
| Method | Iterations | Mean | Error | StdDev |
|--------------------------------------- |----------- |----------:|----------:|----------:|
| ForWithCustomIncrement | 10000 | 4.535 us | 0.0007 us | 0.0007 us |
| ForeachWithEnumerableRange | 10000 | 50.097 us | 0.0050 us | 0.0044 us |
| ForeachWithYieldReturn | 10000 | 52.565 us | 0.0055 us | 0.0049 us |
| ForeachWithRangeEnumerator | 10000 | 4.453 us | 0.0005 us | 0.0004 us |
| ForeachWithRangeEnumeratorRaw | 10000 | 4.453 us | 0.0007 us | 0.0006 us |
| ForeachWithRangeEnumeratorRawWithLocal | 10000 | 17.543 us | 0.0100 us | 0.0094 us |
Ух ты. А кодген покажете? Там нужно в моем бенчмарке раскомментить DisassemblyDiagnoser, а потом посмотреть Artifacts папку
А еще я смотрю у вас Линукс, на нем может по-другому такие вещи работать (я на винде). Интересно посмотреть кодген!
Возможно, у нас просто немного разные релизы .NET.
Вот дизассемблер:
; Benchmarks.ForWithCustomIncrement()
push rbp
mov rbp,rsp
mov eax,[rdi+8]
xor esi,esi
mov edi,[rdi+0C]
xor edx,edx
test eax,eax
jle short M00_L01
M00_L00:
add esi,edx
add edx,edi
cmp edx,eax
jl short M00_L00
M00_L01:
mov eax,esi
pop rbp
ret
; Total bytes of code 30
.NET 6.0.0 (6.0.21.37719), X64 RyuJIT
; Benchmarks.ForeachWithEnumerableRange()
push rbp
push rbx
sub rsp,18
lea rbp,[rsp+20]
mov [rbp-20],rsp
xor ebx,ebx
mov esi,[rdi+8]
xor edi,edi
call System.Linq.Enumerable.Range(Int32, Int32)
mov rdi,rax
mov r11,7FC58A340348
mov rax,7FC58A340348
call qword ptr [rax]
mov rdi,rax
mov [rbp-10],rdi
mov r11,7FC58A340350
mov rax,7FC58A340350
call qword ptr [rax]
test eax,eax
je short M00_L01
M00_L00:
mov rdi,[rbp-10]
mov r11,7FC58A340358
mov rax,7FC58A340358
call qword ptr [rax]
add ebx,eax
mov rdi,[rbp-10]
mov r11,7FC58A340350
mov rax,7FC58A340350
call qword ptr [rax]
test eax,eax
jne short M00_L00
M00_L01:
mov rdi,[rbp-10]
mov r11,7FC58A340360
mov rax,7FC58A340360
call qword ptr [rax]
mov eax,ebx
add rsp,18
pop rbx
pop rbp
ret
push rbp
push rbx
push rax
mov rbp,[rdi]
mov [rsp],rbp
lea rbp,[rbp+20]
cmp qword ptr [rbp-10],0
je short M00_L02
mov rdi,[rbp-10]
mov r11,7FC58A340360
mov rax,7FC58A340360
call qword ptr [rax]
M00_L02:
nop
add rsp,8
pop rbx
pop rbp
ret
; Total bytes of code 233
; System.Linq.Enumerable.Range(Int32, Int32)
push rbp
push r15
push r14
push rbx
push rax
lea rbp,[rsp+20]
mov r14d,edi
mov ebx,esi
movsxd rdi,r14d
movsxd rax,ebx
lea rdi,[rdi+rax-1]
test ebx,ebx
jl short M01_L00
cmp rdi,7FFFFFFF
jg short M01_L00
test ebx,ebx
je short M01_L01
mov rdi,offset MT_System.Linq.Enumerable+RangeIterator
call CORINFO_HELP_NEWSFAST
mov r15,rax
call CORINFO_HELP_GETCURRENTMANAGEDTHREADID
mov [r15+8],eax
mov [r15+14],r14d
add ebx,r14d
mov [r15+18],ebx
mov rax,r15
add rsp,8
pop rbx
pop r14
pop r15
pop rbp
ret
M01_L00:
mov edi,1
call System.Linq.ThrowHelper.ThrowArgumentOutOfRangeException(System.Linq.ExceptionArgument)
int 3
M01_L01:
mov rdi,7FC58B0D0BB0
mov esi,2
call CORINFO_HELP_CLASSINIT_SHARED_DYNAMICCLASS
mov rax,7FC56C005AB8
mov rax,[rax]
add rsp,8
pop rbx
pop r14
pop r15
pop rbp
ret
; Total bytes of code 152
.NET 6.0.0 (6.0.21.37719), X64 RyuJIT
; Benchmarks.ForeachWithYieldReturn()
push rbp
push r15
push r14
push rbx
sub rsp,18
lea rbp,[rsp+30]
mov [rbp-30],rsp
xor ebx,ebx
mov r14d,[rdi+8]
dec r14d
mov rdi,offset MT_Benchmarks+<MyRange>d__10
call CORINFO_HELP_NEWSFAST
mov r15,rax
mov dword ptr [r15+8],0FFFFFFFE
call CORINFO_HELP_GETCURRENTMANAGEDTHREADID
mov [r15+10],eax
xor edi,edi
mov [r15+18],edi
mov [r15+28],r14d
mov dword ptr [r15+20],1
mov rdi,r15
mov r11,7FEE0B690348
mov rax,7FEE0B690348
call qword ptr [rax]
mov r14,rax
mov [rbp-20],r14
mov rdi,r14
mov r11,7FEE0B690350
mov rax,7FEE0B690350
call qword ptr [rax]
test eax,eax
je short M00_L01
M00_L00:
mov rdi,r14
mov r11,7FEE0B690358
mov rax,7FEE0B690358
call qword ptr [rax]
add ebx,eax
mov rdi,r14
mov r11,7FEE0B690350
mov rax,7FEE0B690350
call qword ptr [rax]
test eax,eax
jne short M00_L00
M00_L01:
mov rdi,r14
mov r11,7FEE0B690360
mov rax,7FEE0B690360
call qword ptr [rax]
mov eax,ebx
add rsp,18
pop rbx
pop r14
pop r15
pop rbp
ret
push rbp
push r15
push r14
push rbx
push rax
mov rbp,[rdi]
mov [rsp],rbp
lea rbp,[rbp+30]
cmp qword ptr [rbp-20],0
je short M00_L02
mov rdi,[rbp-20]
mov r11,7FEE0B690360
mov rax,7FEE0B690360
call qword ptr [rax]
M00_L02:
nop
add rsp,8
pop rbx
pop r14
pop r15
pop rbp
ret
; Total bytes of code 299
.NET 6.0.0 (6.0.21.37719), X64 RyuJIT
; Benchmarks.ForeachWithRangeEnumerator()
push rbp
push rbx
sub rsp,18
lea rbp,[rsp+20]
xor ebx,ebx
mov edi,[rdi+8]
dec edi
test edi,edi
jl short M00_L02
mov [rbp-20],ebx
mov [rbp-1C],edi
mov rdi,[rbp-20]
call Library.Extensions.GetEnumerator(System.Range)
mov [rbp-18],rax
mov [rbp-10],edx
mov eax,[rbp-18]
mov edi,[rbp-14]
mov esi,[rbp-10]
add esi,edi
cmp esi,eax
je short M00_L01
nop dword ptr [rax+rax]
M00_L00:
add ebx,esi
add esi,edi
cmp esi,eax
jne short M00_L00
M00_L01:
mov eax,ebx
add rsp,18
pop rbx
pop rbp
ret
M00_L02:
call System.ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException()
int 3
; Total bytes of code 87
; Library.Extensions.GetEnumerator(System.Range)
push rbp
push rbx
sub rsp,18
lea rbp,[rsp+20]
xor eax,eax
mov [rbp-20],rax
mov [rbp-18],rax
mov [rbp-10],rdi
mov edi,[rbp-10]
mov eax,[rbp-0C]
test edi,edi
jge short M01_L01
not edi
test edi,edi
jne near ptr M01_L08
test eax,eax
jge short M01_L00
mov edi,eax
not edi
test edi,edi
jne near ptr M01_L08
mov dword ptr [rbp-20],80000000
mov dword ptr [rbp-1C],1
mov dword ptr [rbp-18],0FFFFFFFF
jmp short M01_L07
M01_L00:
jmp short M01_L03
M01_L01:
test eax,eax
jge short M01_L02
not eax
test eax,eax
jne short M01_L08
dec edi
mov dword ptr [rbp-20],80000000
mov dword ptr [rbp-1C],1
mov [rbp-18],edi
jmp short M01_L07
M01_L02:
jmp short M01_L04
M01_L03:
add eax,2
mov [rbp-20],eax
mov dword ptr [rbp-1C],1
mov dword ptr [rbp-18],0FFFFFFFF
jmp short M01_L07
M01_L04:
cmp edi,eax
jge short M01_L05
inc eax
dec edi
mov esi,1
jmp short M01_L06
M01_L05:
dec eax
inc edi
mov esi,0FFFFFFFF
M01_L06:
mov [rbp-20],eax
mov [rbp-1C],esi
mov [rbp-18],edi
M01_L07:
mov rax,[rbp-20]
mov edx,[rbp-18]
add rsp,18
pop rbx
pop rbp
ret
M01_L08:
mov rdi,offset MT_System.InvalidOperationException
call CORINFO_HELP_NEWSFAST
mov rbx,rax
mov edi,1
mov rsi,7F33C40AC9F8
call CORINFO_HELP_STRCNS
mov rsi,rax
mov rdi,rbx
call System.InvalidOperationException..ctor(System.String)
mov rdi,rbx
call CORINFO_HELP_THROW
int 3
; Total bytes of code 246
Method was not JITted yet.
System.ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException()
.NET 6.0.0 (6.0.21.37719), X64 RyuJIT
; Benchmarks.ForeachWithRangeEnumeratorRaw()
push rbp
push rbx
sub rsp,18
lea rbp,[rsp+20]
xor ebx,ebx
mov edi,[rdi+8]
dec edi
test edi,edi
jl short M00_L02
mov [rbp-20],ebx
mov [rbp-1C],edi
mov rdi,[rbp-20]
call Library.Extensions.GetEnumerator(System.Range)
mov [rbp-18],rax
mov [rbp-10],edx
mov eax,[rbp-18]
mov edi,[rbp-14]
mov esi,[rbp-10]
add esi,edi
cmp esi,eax
je short M00_L01
nop dword ptr [rax+rax]
M00_L00:
add ebx,esi
add esi,edi
cmp esi,eax
jne short M00_L00
M00_L01:
mov eax,ebx
add rsp,18
pop rbx
pop rbp
ret
M00_L02:
call System.ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException()
int 3
; Total bytes of code 87
; Library.Extensions.GetEnumerator(System.Range)
push rbp
push rbx
sub rsp,18
lea rbp,[rsp+20]
xor eax,eax
mov [rbp-20],rax
mov [rbp-18],rax
mov [rbp-10],rdi
mov edi,[rbp-10]
mov eax,[rbp-0C]
test edi,edi
jge short M01_L01
not edi
test edi,edi
jne near ptr M01_L08
test eax,eax
jge short M01_L00
mov edi,eax
not edi
test edi,edi
jne near ptr M01_L08
mov dword ptr [rbp-20],80000000
mov dword ptr [rbp-1C],1
mov dword ptr [rbp-18],0FFFFFFFF
jmp short M01_L07
M01_L00:
jmp short M01_L03
M01_L01:
test eax,eax
jge short M01_L02
not eax
test eax,eax
jne short M01_L08
dec edi
mov dword ptr [rbp-20],80000000
mov dword ptr [rbp-1C],1
mov [rbp-18],edi
jmp short M01_L07
M01_L02:
jmp short M01_L04
M01_L03:
add eax,2
mov [rbp-20],eax
mov dword ptr [rbp-1C],1
mov dword ptr [rbp-18],0FFFFFFFF
jmp short M01_L07
M01_L04:
cmp edi,eax
jge short M01_L05
inc eax
dec edi
mov esi,1
jmp short M01_L06
M01_L05:
dec eax
inc edi
mov esi,0FFFFFFFF
M01_L06:
mov [rbp-20],eax
mov [rbp-1C],esi
mov [rbp-18],edi
M01_L07:
mov rax,[rbp-20]
mov edx,[rbp-18]
add rsp,18
pop rbx
pop rbp
ret
M01_L08:
mov rdi,offset MT_System.InvalidOperationException
call CORINFO_HELP_NEWSFAST
mov rbx,rax
mov edi,1
mov rsi,7FB2CB80C9F8
call CORINFO_HELP_STRCNS
mov rsi,rax
mov rdi,rbx
call System.InvalidOperationException..ctor(System.String)
mov rdi,rbx
call CORINFO_HELP_THROW
int 3
; Total bytes of code 246
Method was not JITted yet.
System.ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException()
.NET 6.0.0 (6.0.21.37719), X64 RyuJIT
; Benchmarks.ForeachWithRangeEnumeratorRawWithLocal()
push rbp
sub rsp,20
lea rbp,[rsp+20]
mov edi,[rdi+8]
dec edi
test edi,edi
jl short M00_L00
xor eax,eax
mov [rbp-18],eax
mov [rbp-14],edi
mov rdi,[rbp-18]
call Library.Extensions.GetEnumerator(System.Range)
mov [rbp-10],rax
mov [rbp-8],edx
mov rdi,[rbp-10]
mov esi,[rbp-8]
add rsp,20
pop rbp
jmp near ptr Benchmarks.<ForeachWithRangeEnumeratorRawWithLocal>g__EnumerateItAll|14_0(Library.RangeEnumerator)
M00_L00:
call System.ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException()
int 3
; Total bytes of code 66
; Library.Extensions.GetEnumerator(System.Range)
push rbp
push rbx
sub rsp,18
lea rbp,[rsp+20]
xor eax,eax
mov [rbp-20],rax
mov [rbp-18],rax
mov [rbp-10],rdi
mov edi,[rbp-10]
mov eax,[rbp-0C]
test edi,edi
jge short M01_L01
not edi
test edi,edi
jne near ptr M01_L08
test eax,eax
jge short M01_L00
mov edi,eax
not edi
test edi,edi
jne near ptr M01_L08
mov dword ptr [rbp-20],80000000
mov dword ptr [rbp-1C],1
mov dword ptr [rbp-18],0FFFFFFFF
jmp short M01_L07
M01_L00:
jmp short M01_L03
M01_L01:
test eax,eax
jge short M01_L02
not eax
test eax,eax
jne short M01_L08
dec edi
mov dword ptr [rbp-20],80000000
mov dword ptr [rbp-1C],1
mov [rbp-18],edi
jmp short M01_L07
M01_L02:
jmp short M01_L04
M01_L03:
add eax,2
mov [rbp-20],eax
mov dword ptr [rbp-1C],1
mov dword ptr [rbp-18],0FFFFFFFF
jmp short M01_L07
M01_L04:
cmp edi,eax
jge short M01_L05
inc eax
dec edi
mov esi,1
jmp short M01_L06
M01_L05:
dec eax
inc edi
mov esi,0FFFFFFFF
M01_L06:
mov [rbp-20],eax
mov [rbp-1C],esi
mov [rbp-18],edi
M01_L07:
mov rax,[rbp-20]
mov edx,[rbp-18]
add rsp,18
pop rbx
pop rbp
ret
M01_L08:
mov rdi,offset MT_System.InvalidOperationException
call CORINFO_HELP_NEWSFAST
mov rbx,rax
mov edi,1
mov rsi,7FCE847FCE78
call CORINFO_HELP_STRCNS
mov rsi,rax
mov rdi,rbx
call System.InvalidOperationException..ctor(System.String)
mov rdi,rbx
call CORINFO_HELP_THROW
int 3
; Total bytes of code 246
; Benchmarks.<ForeachWithRangeEnumeratorRawWithLocal>g__EnumerateItAll|14_0(Library.RangeEnumerator)
push rbp
sub rsp,10
lea rbp,[rsp+10]
mov [rbp-10],rdi
mov [rbp-8],esi
xor eax,eax
mov edi,[rbp-8]
add edi,[rbp-0C]
mov [rbp-8],edi
mov esi,[rbp-10]
cmp edi,esi
je short M02_L01
M02_L00:
mov edi,[rbp-8]
add eax,edi
add edi,[rbp-0C]
mov [rbp-8],edi
cmp edi,esi
jne short M02_L00
M02_L01:
add rsp,10
pop rbp
ret
; Total bytes of code 56
Method was not JITted yet.
System.ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException()
Интересно. В последнем методе (который WithLocal) у вас JIT решил использовать jmp вместо call-а:
...
jmp near ptr Benchmarks.<ForeachWithRangeEnumeratorRawWithLocal>g__EnumerateItAll|14_0(Library.RangeEnumerator)
...
Поэтому итерация цикла в локальной функции выглядит как
M02_L00:
mov edi,[rbp-8]
add eax,edi
add edi,[rbp-0C]
mov [rbp-8],edi
cmp edi,esi
jne short M02_L00
А вот там, где у меня он разместил структуру на стеке, у вас он смог разместить ее в регистрах:
M00_L00:
add ebx,esi
add esi,edi
cmp esi,eax
jne short M00_L00
Видимо связано это с тем, что на линуксе джит может больше локальных переменных в регистры положить, но я не уверен, может тут есть эксперты?
А насчет jmp-а получается, что джит ошибся, и все-таки нужен был call. Интересно, а если вы добавите [MethodImpl(MethodImplOptions.NoInlining)]
на локальную функцию, мы получим call?
BenchmarkDotNet=v0.13.1, OS=ubuntu 21.04
AMD FX(tm)-8120, 1 CPU, 8 logical and 4 physical cores
.NET SDK=5.0.400
[Host] : .NET 5.0.9 (5.0.921.35908), X64 RyuJIT
DefaultJob : .NET 5.0.9 (5.0.921.35908), X64 RyuJIT
| Method | Iterations | Mean | Error | StdDev | Median |
|--------------------------------------- |----------- |----------:|----------:|-----------:|----------:|
| ForWithCustomIncrement | 10000 | 5.911 us | 0.0171 us | 0.0152 us | 5.906 us |
| ForeachWithEnumerableRange | 10000 | 68.460 us | 1.3414 us | 1.5968 us | 67.668 us |
| ForeachWithYieldReturn | 10000 | 89.176 us | 4.1332 us | 11.7252 us | 84.396 us |
| ForeachWithRangeEnumerator | 10000 | 6.087 us | 0.0410 us | 0.0364 us | 6.076 us |
| ForeachWithRangeEnumeratorRaw | 10000 | 9.171 us | 0.0918 us | 0.0766 us | 9.152 us |
| ForeachWithRangeEnumeratorRawWithLocal | 10000 | 27.249 us | 0.1797 us | 0.1501 us | 27.258 us |
Это без атрибута NoInlining, а это с ней
BenchmarkDotNet=v0.13.1, OS=ubuntu 21.04
AMD FX(tm)-8120, 1 CPU, 8 logical and 4 physical cores
.NET SDK=5.0.400
[Host] : .NET 5.0.9 (5.0.921.35908), X64 RyuJIT
DefaultJob : .NET 5.0.9 (5.0.921.35908), X64 RyuJIT
| Method | Iterations | Mean | Error | StdDev |
|--------------------------------------- |----------- |----------:|----------:|----------:|
| ForWithCustomIncrement | 10000 | 6.106 us | 0.0520 us | 0.0461 us |
| ForeachWithEnumerableRange | 10000 | 70.015 us | 1.3127 us | 1.1637 us |
| ForeachWithYieldReturn | 10000 | 77.383 us | 1.5419 us | 2.0584 us |
| ForeachWithRangeEnumerator | 10000 | 6.115 us | 0.0234 us | 0.0208 us |
| ForeachWithRangeEnumeratorRaw | 10000 | 6.128 us | 0.0349 us | 0.0292 us |
| ForeachWithRangeEnumeratorRawWithLocal | 10000 | 27.460 us | 0.2394 us | 0.1999 us |
// * Hints *
Outliers
Benchmarks.ForWithCustomIncrement: Default -> 1 outlier was removed (6.26 us)
Benchmarks.ForeachWithEnumerableRange: Default -> 1 outlier was removed (76.72 us)
Benchmarks.ForeachWithYieldReturn: Default -> 2 outliers were removed (86.81 us, 91.80 us)
Benchmarks.ForeachWithRangeEnumerator: Default -> 1 outlier was removed (6.18 us)
Benchmarks.ForeachWithRangeEnumeratorRaw: Default -> 2 outliers were removed (6.23 us, 6.24 us)
Benchmarks.ForeachWithRangeEnumeratorRawWithLocal: Default -> 2 outliers were removed (28.46 us, 29.80 us)
me@Bella:~$ uname -a Linux Bella 5.11.0-34-generic #36-Ubuntu SMP Thu Aug 26 19:22:09 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
Ну и собственно вопрос, где "научиться плохому" или хорошо разбираться в IL?
Так, я на всякий случай уточню - вы сюда
[Benchmark]
// НЕ СЮДА
public int ForeachWithRangeEnumeratorRawWithLocal()
{
var enumerator = (0..(Iterations - 1)).GetEnumerator();
return EnumerateItAll(enumerator);
// А СЮДА?
static int EnumerateItAll(RangeEnumerator enumerator)
{
var a = 0;
while (enumerator.MoveNext())
a += enumerator.Current;
return a;
}
}
вешали? Т. е. не на внешнюю функцию, а на локальную?
хорошо разбираться в IL?
На самом деле IL мы тут вообще не изучали. В основном здесь все от джита зависит
Совершенно верно
[Benchmark]
public int ForeachWithRangeEnumeratorRawWithLocal()
{
var enumerator = (0..(Iterations - 1)).GetEnumerator();
return EnumerateItAll(enumerator);
[MethodImpl(MethodImplOptions.NoInlining)]
static int EnumerateItAll(RangeEnumerator enumerator)
{
var a = 0;
while (enumerator.MoveNext())
a += enumerator.Current;
return a;
}
}
Пересобрал с разкомментированным DisassemblyDiagnoser, еще раз приведу результат
BenchmarkDotNet=v0.13.1, OS=ubuntu 21.04
AMD FX(tm)-8120, 1 CPU, 8 logical and 4 physical cores
.NET SDK=5.0.400
[Host] : .NET 5.0.9 (5.0.921.35908), X64 RyuJIT
DefaultJob : .NET 5.0.9 (5.0.921.35908), X64 RyuJIT
| Method | Iterations | Mean | Error | StdDev | Code Size |
|--------------------------------------- |----------- |----------:|----------:|----------:|----------:|
| ForWithCustomIncrement | 10000 | 6.125 us | 0.0586 us | 0.0549 us | 30 B |
| ForeachWithEnumerableRange | 10000 | 69.712 us | 1.1155 us | 1.2399 us | 433 B |
| ForeachWithYieldReturn | 10000 | 77.253 us | 1.4753 us | 2.0681 us | 365 B |
| ForeachWithRangeEnumerator | 10000 | 6.171 us | 0.0640 us | 0.0567 us | 340 B |
| ForeachWithRangeEnumeratorRaw | 10000 | 6.228 us | 0.0513 us | 0.0480 us | 340 B |
| ForeachWithRangeEnumeratorRawWithLocal | 10000 | 27.767 us | 0.1562 us | 0.1461 us | 370 B |
Мне честно говоря было лень копировать простыню под кат, загрузил на github https://github.com/iamkisly/ForHabrahabr/blob/main/FastEnum_with_NoInlining_attr_01092021_2040/Benchmarks-asm.md#net-509-5092135908-x64-ryujit-5
Удивительно. У человека сверху хотя бы я объяснял это тем, что там джамп вместо колла. Почему здесь джит не смог сделать что надо, при том, что там колл - непонятно. Может на шестом дотнете получится?
Результат почти не поменялся, ну в пределах флуктуаций.
BenchmarkDotNet=v0.13.1, OS=ubuntu 21.04
AMD FX(tm)-8120, 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.100-preview.7.21379.14
[Host] : .NET 6.0.0 (6.0.21.37719), X64 RyuJIT
DefaultJob : .NET 6.0.0 (6.0.21.37719), X64 RyuJIT
| Method | Iterations | Mean | Error | StdDev | Code Size |
|--------------------------------------- |----------- |----------:|----------:|----------:|----------:|
| ForWithCustomIncrement | 10000 | 6.147 us | 0.0387 us | 0.0343 us | 30 B |
| ForeachWithEnumerableRange | 10000 | 64.961 us | 0.7376 us | 0.6539 us | 385 B |
| ForeachWithYieldReturn | 10000 | 79.919 us | 0.7037 us | 0.5876 us | 299 B |
| ForeachWithRangeEnumerator | 10000 | 6.182 us | 0.0260 us | 0.0243 us | 333 B |
| ForeachWithRangeEnumeratorRaw | 10000 | 6.199 us | 0.0362 us | 0.0338 us | 333 B |
| ForeachWithRangeEnumeratorRawWithLocal | 10000 | 28.486 us | 0.2395 us | 0.2000 us | 368 B |
Линк на сренерированный markdown
Для любопытных, обновил платформу до Ryzen7.. картина та же
С использованием [MethodImpl(MethodImplOptions.AggressiveInlining)]
| Method | Iterations | Mean | Error | StdDev |
|--------------------------------------- |----------- |----------:|----------:|----------:|
| ForWithCustomIncrement | 10000 | 6.038 us | 0.0337 us | 0.0316 us |
| ForeachWithEnumerableRange | 10000 | 63.210 us | 0.8231 us | 0.7699 us |
| ForeachWithYieldReturn | 10000 | 78.043 us | 1.2545 us | 1.1735 us |
| ForeachWithRangeEnumerator | 10000 | 6.090 us | 0.0416 us | 0.0369 us |
| ForeachWithRangeEnumeratorRaw | 10000 | 6.094 us | 0.0239 us | 0.0224 us |
| ForeachWithRangeEnumeratorRawWithLocal | 10000 | 9.214 us | 0.0637 us | 0.0532 us |
Перегрузился в Windows — всё стало как у вас. Также я вообще не заметил никакого эффекта ни от [MethodImpl(MethodImplOptions.NoInlining)]
, ни от [MethodImpl(MethodImplOptions.AggressiveInlining)]
. В Linux не пробовал.
foreach (var i in 1..5)
Console.Write(i);
(выводит 12345)
Каноничней выводить 1234.
Это по каким канонам "каноничнее"?
Всем, где нумеруют индексы с нуля.
Во многих языках правая граница не включена в отрезок(питон, раст, свифт, етц) При таком подходе апи становится более стройнее, особенно если нужно еще ходить по самого конца
Даже в шарпе 1..5 — 5 не входит в интервал — см синтакс для range based indices.
Во всех других языках аналогично: правая граница не включена.
В сфите, например, для включения ее в интервал нужно написать вместо двух три точки: 1…5 vs 1..5
Ускоряем цикл foreach до for