Как стать автором
Обновить

Комментарии 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-asm.md
.NET 6.0.0 (6.0.21.37719), X64 RyuJIT
; 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 не пробовал.

Я говорю именно о том, чтобы повесить NoInlining на локальную функцию в последнем методе на линуксе. Кажется, тогда тоже будет так же быстро, как фор

Получилось бы внедрить такую оптимизацию в Рослин?

Вряд ли, Розлин не занимается оптимизациями. А вот RyuJIT с помощью Dynamic PGO имеет потенциал сделать магию, на ходу поменяв код, выгружая в регистры "горячие" локальные переменные.

foreach (var i in 1..5)

Console.Write(i);

(выводит 12345)

Каноничней выводить 1234.

Это по каким канонам "каноничнее"?

Всем, где нумеруют индексы с нуля.

Во многих языках правая граница не включена в отрезок(питон, раст, свифт, етц) При таком подходе апи становится более стройнее, особенно если нужно еще ходить по самого конца

Даже в шарпе 1..5 — 5 не входит в интервал — см синтакс для range based indices.

Во всех других языках аналогично: правая граница не включена.

В сфите, например, для включения ее в интервал нужно написать вместо двух три точки: 1…5 vs 1..5

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории