Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Поговорим о микрооптимизациях .NET-приложений

662 views

Published on

Доклад для Middle и Senior .NET-программистов о микроптимизациях приложения, из которого Вы узнаете:

О том, как важно понимать IL и ASM код, соответствующий вашей C#-программе;
О различных уровнях микрооптимизаций начиная от C# и JIT компиляторов, заканчивая CPU;
Об особенностях оптимизаций под различные процессорные архитектуры;
Об отличиях разных версиях JIT-компиляторов, включая RyuJIT;
О том, как правильно замерять время выполнения приложений и оценивать эффективность оптимизаций.

Доклад будет полезен всем разработчикам, которые хотят хотят сделать свои и без того быстрые программы ещё на 5-10% быстрее.

Published in: Software
  • Be the first to comment

Поговорим о микрооптимизациях .NET-приложений

  1. 1. Поговорим о микрооптимизациях .NET-приложений Андрей Акиньшин, Энтерра .NEXT 2015 Spb 1/50
  2. 2. Поговорим про общую теорию Premature optimization is the root of all evil. c Donald Ervin Knuth 2/50 Theory
  3. 3. С чего начать? Хочу, чтобы программа работала быстрее! 3/50 Theory
  4. 4. С чего начать? Хочу, чтобы программа работала быстрее! • Хорошая архитектура • Эффективные алгоритмы • Правильные структуры данных • Разумное использование памяти • I/O • Networking • Кэш • ... 3/50 Theory
  5. 5. С чего начать? Хочу, чтобы программа работала быстрее! • Хорошая архитектура • Эффективные алгоритмы • Правильные структуры данных • Разумное использование памяти • I/O • Networking • Кэш • ... Но мы не будем про это разговаривать... 3/50 Theory
  6. 6. Под что будем оптимизировать? 4/50 Theory
  7. 7. Под что будем оптимизировать? • Версия CLR: CLR2? CLR4? CoreCLR? Mono? • Версия ОС: Windows? Linux? MacOS? • Версия JIT: x86? x64? RyuJIT? • Версия GC: MS (какой CLR?)? Mono (Boehm/Sgen)? • Компиляция: JIT? NGen? Есть ли MPGO? .NET Native? • Разное железо: тысячи его. 4/50 Theory
  8. 8. Микробенчмаркинг — это сложно Чтобы увеличить скорость работы, нужно сначала научиться её измерять, а для этого: 5/50 Theory
  9. 9. Микробенчмаркинг — это сложно Чтобы увеличить скорость работы, нужно сначала научиться её измерять, а для этого: • Запускаем бенчмарки в разных процессах. • Делаем грамотный прогрев и подсчёт статистик. • Проверяем разные окружения (и не забываем про RyuJIT). • Побеждаем сайд-эффекты. 5/50 Theory
  10. 10. Микробенчмаркинг — это сложно Чтобы увеличить скорость работы, нужно сначала научиться её измерять, а для этого: • Запускаем бенчмарки в разных процессах. • Делаем грамотный прогрев и подсчёт статистик. • Проверяем разные окружения (и не забываем про RyuJIT). • Побеждаем сайд-эффекты. • Понимаем устройство .NET, устройство ОС, устройство CPU и устройство вселенной. 5/50 Theory
  11. 11. Микробенчмаркинг — это сложно Чтобы увеличить скорость работы, нужно сначала научиться её измерять, а для этого: • Запускаем бенчмарки в разных процессах. • Делаем грамотный прогрев и подсчёт статистик. • Проверяем разные окружения (и не забываем про RyuJIT). • Побеждаем сайд-эффекты. • Понимаем устройство .NET, устройство ОС, устройство CPU и устройство вселенной. На правах рекламы: https://github.com/PerfDotNet/BenchmarkDotNet/ https://www.nuget.org/packages/BenchmarkDotNet/ c Andrey Akinshin, Jon Skeet, Matt Warren 5/50 Theory
  12. 12. Микробенчмаркинг — это сложно Чтобы увеличить скорость работы, нужно сначала научиться её измерять, а для этого: • Запускаем бенчмарки в разных процессах. • Делаем грамотный прогрев и подсчёт статистик. • Проверяем разные окружения (и не забываем про RyuJIT). • Побеждаем сайд-эффекты. • Понимаем устройство .NET, устройство ОС, устройство CPU и устройство вселенной. На правах рекламы: https://github.com/PerfDotNet/BenchmarkDotNet/ https://www.nuget.org/packages/BenchmarkDotNet/ c Andrey Akinshin, Jon Skeet, Matt Warren Сегодня мы будем проводить измерения в попугаях. 5/50 Theory
  13. 13. Поговорим про С#-компилятор и IL You should always understand at least one layer below what you are coding. c Lee Campbell 6/50 Compiler+IL
  14. 14. Поговорим про switch switch (x) { case 0: return 0; case 1: return 1; case 2: return 2; case 3: return 3; } return -1; 7/50 Compiler+IL
  15. 15. А что внутри? ; Фаза 1: switch IL_0008: switch (IL_001f, IL_0021, IL_0023, IL_0025) IL_001d: br.s IL_0027 ; Фаза 2: cases IL_001f: ldc.i4.0 IL_0020: ret IL_0021: ldc.i4.1 IL_0022: ret IL_0023: ldc.i4.2 IL_0024: ret IL_0025: ldc.i4.3 IL_0026: ret IL_0027: ldc.i4.m1 IL_0028: ret 8/50 Compiler+IL
  16. 16. Поговорим про switch switch (x) { case 0: return 0; case 1000: return 1000; case 2000: return 2000; case 3000: return 3000; } return -1; 9/50 Compiler+IL
  17. 17. А что внутри? ; Фаза 1: switch IL_0007: ldloc.0 IL_0008: ldc.i4 1000 IL_000d: bgt.s IL_001c IL_000f: ldloc.0 IL_0010: brfalse.s IL_002e IL_0012: ldloc.0 IL_0013: ldc.i4 1000 IL_0018: beq.s IL_0030 IL_001a: br.s IL_0042 IL_001c: ldloc.0 IL_001d: ldc.i4 2000 IL_0022: beq.s IL_0036 IL_0024: ldloc.0 IL_0025: ldc.i4 3000 IL_002a: beq.s IL_003c IL_002c: br.s IL_0042 ; Фаза 2: cases IL_002e: ldc.i4.0 IL_002f: ret IL_0030: ldc.i4 1000 IL_0035: ret IL_0036: ldc.i4 2000 IL_003b: ret IL_003c: ldc.i4 3000 IL_0041: ret IL_0042: ldc.i4.m1 IL_0043: ret 10/50 Compiler+IL
  18. 18. Поговорим про switch switch (x) { case "a": return "A"; case "b": return "B"; case "c": return "C"; case "d": return "D"; } return ""; 11/50 Compiler+IL
  19. 19. А что внутри? ; Фаза 1: switch IL_0007: ldloc.0 IL_0008: ldstr "a" IL_000d: call bool String::op_Equality IL_0012: brtrue.s IL_003d IL_0014: ldloc.0 IL_0015: ldstr "b" IL_001a: call bool String::op_Equality IL_001f: brtrue.s IL_0043 IL_0021: ldloc.0 IL_0022: ldstr "c" IL_0027: call bool String::op_Equality IL_002c: brtrue.s IL_0049 IL_002e: ldloc.0 IL_002f: ldstr "d" IL_0034: call bool String::op_Equality IL_0039: brtrue.s IL_004f IL_003b: br.s IL_0055 ; Фаза 2: cases IL_003d: ldstr "A" IL_0042: ret IL_0043: ldstr "B" IL_0048: ret IL_0049: ldstr "C" IL_004e: ret IL_004f: ldstr "D" IL_0054: ret IL_0055: ldstr "" IL_005a: ret 12/50 Compiler+IL
  20. 20. Поговорим про switch switch (x) { case "a": return "A"; case "b": return "B"; case "c": return "C"; case "d": return "D"; case "e": return "E"; case "f": return "F"; case "g": return "G"; } return ""; 13/50 Compiler+IL
  21. 21. А что внутри? ; Старый компилятор ; Фаза 0: Dictionary<string, string> IL_000d: volatile. IL_000f: ldsfld class Dictionary<string, int32> IL_0014: brtrue.s IL_0077 IL_0016: ldc.i4.7 IL_0017: newobj instance void class Dictionary<string, int32>::.ctor IL_001c: dup IL_001d: ldstr "a" IL_0022: ldc.i4.0 IL_0023: call instance void class Dictionary<string, int32>::Add IL_0028: dup IL_0029: ldstr "b" IL_002e: ldc.i4.1 IL_0023: call instance void class Dictionary<string, int32>::Add IL_0034: dup IL_0035: ldstr "c" ; ... ; Фаза 1: IL_0088: ldloc.1 IL_0089: switch ( IL_00ac, IL_00b2, IL_00b8, IL_00be, IL_00c4, IL_00ca, IL_00d0) IL_00aa: br.s IL_00d6 ; Фаза 2: cases IL_00ac: ldstr "A" IL_00b1: ret IL_00b2: ldstr "B" IL_00b7: ret IL_00b8: ldstr "C" IL_00bd: ret IL_00be: ldstr "D" IL_00c3: ret IL_00c4: ldstr "E" IL_00c9: ret IL_00ca: ldstr "F" IL_00cf: ret IL_00d0: ldstr "G" IL_00d5: ret IL_00d6: ldstr "" IL_00db: ret 14/50 Compiler+IL
  22. 22. А что внутри? // Roslyn // Фаза 1: ComputeStringHash uint num = ComputeStringHash(x); // Фаза 2: Бинарный поиск if (num <= 3792446982u) if (num != 3758891744u) if (num != 3775669363u) if (num == 3792446982u) if (text == "g") return "G"; else if (text == "d") return "D"; else if (text == "e") return "E"; else if (num <= 3826002220u) if (num != 3809224601u) if (num == 3826002220u) if (text == "a") return "A"; else if (text == "f") return "F"; else if (num != 3859557458u) if (num == 3876335077u) if (text == "b") return "B"; else if (text == "c") return "C"; return ""; 15/50 Compiler+IL
  23. 23. История 16/50 Compiler+IL
  24. 24. Поговорим про Readonly fields public struct Int256 { private readonly long bits0, bits1, bits2, bits3; public Int256(long bits0, long bits1, long bits2, long bits3) { this.bits0 = bits0; this.bits1 = bits1; this.bits2 = bits2; this.bits3 = bits3; } public long Bits0 => bits0; public long Bits1 => bits1; public long Bits2 => bits2; public long Bits3 => bits3; } private Int256 a = new Int256(1L, 5L, 10L, 100L); private readonly Int256 b = new Int256(1L, 5L, 10L, 100L); [Benchmark] public long GetValue() => a.Bits0 + a.Bits1 + a.Bits2 + a.Bits3; [Benchmark] public long GetReadOnlyValue() => b.Bits0 + b.Bits1 + b.Bits2 + b.Bits3; 17/50 Compiler+IL
  25. 25. Поговорим про Readonly fields public struct Int256 { private readonly long bits0, bits1, bits2, bits3; public Int256(long bits0, long bits1, long bits2, long bits3) { this.bits0 = bits0; this.bits1 = bits1; this.bits2 = bits2; this.bits3 = bits3; } public long Bits0 => bits0; public long Bits1 => bits1; public long Bits2 => bits2; public long Bits3 => bits3; } private Int256 a = new Int256(1L, 5L, 10L, 100L); private readonly Int256 b = new Int256(1L, 5L, 10L, 100L); [Benchmark] public long GetValue() => a.Bits0 + a.Bits1 + a.Bits2 + a.Bits3; [Benchmark] public long GetReadOnlyValue() => b.Bits0 + b.Bits1 + b.Bits2 + b.Bits3; LegacyJIT-x64 RyuJIT-x64 GetValue 1 попугай 1 попугай GetReadOnlyValue 6.2 попугая 7.6 попугая 17/50 Compiler+IL
  26. 26. Как же так? ; GetValue IL_0000: ldarg.0 IL_0001: ldflda valuetype Program::a IL_0006: call instance int64 Int256::get_Bits0() ; GetReadOnlyValue IL_0000: ldarg.0 IL_0001: ldfld valuetype Program::b IL_0006: stloc.0 IL_0007: ldloca.s 0 IL_0009: call instance int64 Int256::get_Bits0() См. также: Jon Skeet, Micro-optimization: the surprising inefficiency of readonly fields 18/50 Compiler+IL
  27. 27. Поговорим про JIT и ASM 19/50 JIT+ASM
  28. 28. Наши новые друзья • COMPLUS_JitDump • COMPLUS_JitDisasm • COMPLUS_JitDiffableDasm • COMPLUS_JitGCDump • COMPLUS_JitUnwindDump • COMPLUS_JitEHDump • COMPLUS_JitTimeLogFile • COMPLUS_JitTimeLogCsv См. также: Book of the Runtime, JIT Compiler Structure 20/50 JIT+ASM
  29. 29. Array bound check elimination const int N = 11; int[] a = new int[N]; for (int i = 0; i < N; i++) sum += a[i]; VS for (int i = 0; i < a.Length; i++) sum += a[i]; 21/50 JIT+ASM
  30. 30. Array bound check elimination const int N = 11; int[] a = new int[N]; for (int i = 0; i < N; i++) sum += a[i]; VS for (int i = 0; i < a.Length; i++) sum += a[i]; LegacyJIT-x86 LegacyJIT-x64 RyuJIT-x64 N 1.3 попугая 1 попугай 1.6 попугая a.Length 1.2 попугая 1 попугай 1.5 попугая 21/50 JIT+ASM
  31. 31. Как же так? LegacyJIT-x86 ; LegacyJIT-x86, N ; ... Loop: ; if i >= a.Length cmp eax,edx ; then throw Exception jae 004635F9 ; sum += a[i] add ecx,dword ptr [esi+eax*4+8] ; i++ inc eax ; if i < N cmp eax,0Bh ; then continue jl Loop ; LegacyJIT-x86, a.Length ; ... ; ... ; ... ; ... ; ... Loop: ; sum += a[i] add ecx,dword ptr [esi+edx*4+8] ; i++ inc edx ; if i < a.Length cmp eax,edx ; then continue jg Loop 22/50 JIT+ASM
  32. 32. Как же так? LegacyJIT-x64 ; LegacyJIT-x64, N ; ... Loop: ; eax = x[i] mov eax,dword ptr [r8+rcx+10h] ; sum += a[i] add edx,eax ; i++ add rcx,4 ; if i < N cmp rcx,2Ch ; then continue jl Loop ; LegacyJIT-x64, a.Length ; ... Loop: ; eax = x[i] mov eax,dword ptr [r9+r8+10h] ; sum += a[i] add ecx,eax ; i++ inc edx add r8,4 ; if i < N cmp edx,r10d ; then continue jl Loop 23/50 JIT+ASM
  33. 33. Как же так? RyuJIT-x64 ; RyuJIT-x64, N Loop: ; r8 = a.Length mov r8d,dword ptr [rax+8] ; if i >= a.Length cmp ecx,r8d ; then throw exception jae 00007FF94F9B46B4 ; r8 = i movsxd r8,ecx ; r8 = a[i] mov r8d,dword ptr [rax+r8*4+10h] ; sum += a[i] add edx,r8d ; i++ inc ecx ; if i < N cmp ecx,0Bh ; then continue jl Loop ; RyuJIT-x64, a.Length ; ... ; ... ; ... ; ... ; ... ; ... Loop: ; r9 = i movsxd r9,ecx ; r9 = x[i] mov r9d,dword ptr [rax+r9*4+10h] ; sum += x[i] add edx,r9d ; i++ inc ecx ; if i < a.Length cmp r8d,ecx ; then continue jg Loop 24/50 JIT+ASM
  34. 34. История 25/50 JIT+ASM
  35. 35. Inlining // mscorlib/system/decimal.cs,158 // Constructs a Decimal from an integer value. public Decimal(int value) { // JIT today can’t inline methods that contains "starg" // opcode. For more details, see DevDiv Bugs 81184: // x86 JIT CQ: Removing the inline striction of "starg". int value_copy = value; if (value_copy >= 0) { flags = 0; } else { flags = SignMask; value_copy = -value_copy; } lo = value_copy; mid = 0; hi = 0; } 26/50 JIT+ASM
  36. 36. Inlining [Benchmark] int Calc() => WithoutStarg(0x11) + WithStarg(0x12); int WithoutStarg(int value) => value; int WithStarg(int value) { if (value < 0) value = -value; return value; } 27/50 JIT+ASM
  37. 37. Inlining [Benchmark] int Calc() => WithoutStarg(0x11) + WithStarg(0x12); int WithoutStarg(int value) => value; int WithStarg(int value) { if (value < 0) value = -value; return value; } LegacyJIT-x86 LegacyJIT-x64 RyuJIT-x64 1 попугай 0 попугаев 1 попугай 27/50 JIT+ASM
  38. 38. Как же так? LegacyJIT-x64 ; LegacyJIT-x64 mov ecx,23h ret 28/50 JIT+ASM
  39. 39. Как же так? LegacyJIT-x64 ; LegacyJIT-x64 mov ecx,23h ret RyuJIT-x64 // Inline expansion aborted due to opcode // [06] OP_starg.s in method // Program:WithStarg(int):int:this 28/50 JIT+ASM
  40. 40. История 29/50 JIT+ASM
  41. 41. Loop unrolling int sum = 0; for (int i = 0; i < 1024; i++) sum += i; 30/50 JIT+ASM
  42. 42. Loop unrolling int sum = 0; for (int i = 0; i < 1024; i++) sum += i; LegacyJIT-x86 LegacyJIT-x64 RyuJIT-x64 1.0 попугай 0.9 попугая 1.0 попугай 30/50 JIT+ASM
  43. 43. Как же так? LegacyJIT-x64 xor ecx,ecx mov edx,1 Loop: lea eax,[rdx-1] add ecx,eax ; sum += (i) add ecx,edx ; sum += (i+1) lea eax,[rdx+1] add ecx,eax ; sum += (i+2) lea eax,[rdx+2] add ecx,eax ; sum += (i+3) add edx,4 ; i += 4 cmp edx,401h jl Loop 31/50 JIT+ASM
  44. 44. Поговорим про параллелизм 32/50 Parallelism
  45. 45. Поговорим про параллелизм Но не будем разговаривать про потоки, задачи, PLINQ и прочий мультитрединг... 32/50 Parallelism
  46. 46. Поговорим про SIMD private struct MyVector { public float X, Y, Z, W; public MyVector(float x, float y, float z, float w) { X = x; Y = y; Z = z; W = w; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MyVector operator *(MyVector left, MyVector right) { return new MyVector(left.X * right.X, left.Y * right.Y, left.Z * right.Z, left.W * right.W); } } private Vector4 vector1, vector2, vector3; private MyVector myVector1, myVector2, myVector3; [Benchmark] public void MyMul() => myVector3 = myVector1 * myVector2; [Benchmark] public void BclMul() => vector3 = vector1 * vector2; 33/50 Parallelism
  47. 47. Поговорим про SIMD private struct MyVector { public float X, Y, Z, W; public MyVector(float x, float y, float z, float w) { X = x; Y = y; Z = z; W = w; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MyVector operator *(MyVector left, MyVector right) { return new MyVector(left.X * right.X, left.Y * right.Y, left.Z * right.Z, left.W * right.W); } } private Vector4 vector1, vector2, vector3; private MyVector myVector1, myVector2, myVector3; [Benchmark] public void MyMul() => myVector3 = myVector1 * myVector2; [Benchmark] public void BclMul() => vector3 = vector1 * vector2; LegacyJIT-x64 RyuJIT-x64 MyMul 34 попугая 5 попугаев BclMul 34 попугая 1 попугай 33/50 Parallelism
  48. 48. Как же так? ; LegacyJIT-x64 ; MyMul, BclMul ; ... movss xmm3,dword ptr [rsp+40h] mulss xmm3,dword ptr [rsp+30h] movss xmm2,dword ptr [rsp+44h] mulss xmm2,dword ptr [rsp+34h] movss xmm1,dword ptr [rsp+48h] mulss xmm1,dword ptr [rsp+38h] movss xmm0,dword ptr [rsp+4Ch] mulss xmm0,dword ptr [rsp+3Ch] xor eax,eax mov qword ptr [rsp],rax mov qword ptr [rsp+8],rax lea rax,[rsp] movss dword ptr [rax],xmm3 movss dword ptr [rax+4],xmm2 movss dword ptr [rax+8],xmm1 movss dword ptr [rax+0Ch],xmm0 ; ... ; RyuJIT-x64 ; MyMul ; ... vmulss xmm0,xmm0,xmm4 vmulss xmm1,xmm1,xmm5 vmulss xmm2,xmm2,xmm6 vmulss xmm3,xmm3,xmm7 ; ... ; BclMul vmovupd xmm0,xmmword ptr [rcx+8] vmovupd xmm1,xmmword ptr [rcx+18h] vmulps xmm0,xmm0,xmm1 vmovupd xmmword ptr [rcx+28h],xmm0 34/50 Parallelism
  49. 49. Думаем про ASM double x = /* ... */ ; double a = x + 1; double b = x * 2; double c = Math.Sqrt(x); 35/50 Parallelism
  50. 50. Думаем про ASM double x = /* ... */ ; double a = x + 1; double b = x * 2; double c = Math.Sqrt(x); LegacyJIT-x86 LegacyJIT-x64 RyuJIT-x64 (x87 FPU) (SSE2) (AVX) x + 1 faddp addsd vaddsd x * 2 fmul mulsd vmulsd Sqrt(X) fsqrt sqrtsd vsqrtsd 35/50 Parallelism
  51. 51. История 36/50 Parallelism
  52. 52. Задачка double Sqrt13() => Math.Sqrt(1) + Math.Sqrt(2) + Math.Sqrt(3) + /* ... */ + Math.Sqrt(13); VS double Sqrt14() => Math.Sqrt(1) + Math.Sqrt(2) + Math.Sqrt(3) + /* ... */ + Math.Sqrt(13) + Math.Sqrt(14); 37/50 Parallelism
  53. 53. Задачка double Sqrt13() => Math.Sqrt(1) + Math.Sqrt(2) + Math.Sqrt(3) + /* ... */ + Math.Sqrt(13); VS double Sqrt14() => Math.Sqrt(1) + Math.Sqrt(2) + Math.Sqrt(3) + /* ... */ + Math.Sqrt(13) + Math.Sqrt(14); RyuJIT-x641 Sqrt13 40 попугаев Sqrt14 1 попугай 1 RyuJIT RC 37/50 Parallelism
  54. 54. Как же так? RyuJIT-x64, Sqrt13 vsqrtsd xmm0,xmm0,mmword ptr [7FF94F9E4D28h] vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D30h] vaddsd xmm0,xmm0,xmm1 vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D38h] vaddsd xmm0,xmm0,xmm1 vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D40h] vaddsd xmm0,xmm0,xmm1 vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D48h] vaddsd xmm0,xmm0,xmm1 vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D50h] vaddsd xmm0,xmm0,xmm1 vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D58h] vaddsd xmm0,xmm0,xmm1 vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D60h] vaddsd xmm0,xmm0,xmm1 vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D68h] vaddsd xmm0,xmm0,xmm1 vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D70h] vaddsd xmm0,xmm0,xmm1 vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D78h] vaddsd xmm0,xmm0,xmm1 vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D80h] vaddsd xmm0,xmm0,xmm1 vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D88h] vaddsd xmm0,xmm0,xmm1 ret 38/50 Parallelism
  55. 55. Как же так? RyuJIT-x64, Sqrt14 vmovsd xmm0,qword ptr [7FF94F9C4C80h] ret 39/50 Parallelism
  56. 56. Как же так? Большое дерево выражения * stmtExpr void (top level) (IL 0x000... ???) | /--* mathFN double sqrt | | --* dconst double 13.000000000000000 | /--* + double | | | /--* mathFN double sqrt | | | | --* dconst double 12.000000000000000 | | --* + double | | | /--* mathFN double sqrt | | | | --* dconst double 11.000000000000000 | | --* + double | | | /--* mathFN double sqrt | | | | --* dconst double 10.000000000000000 | | --* + double | | | /--* mathFN double sqrt | | | | --* dconst double 9.0000000000000000 | | --* + double | | | /--* mathFN double sqrt | | | | --* dconst double 8.0000000000000000 | | --* + double | | | /--* mathFN double sqrt | | | | --* dconst double 7.0000000000000000 | | --* + double | | | /--* mathFN double sqrt | | | | --* dconst double 6.0000000000000000 | | --* + double | | | /--* mathFN double sqrt | | | | --* dconst double 5.0000000000000000 // ... 40/50 Parallelism
  57. 57. Как же так? Constant folding в действии N001 [000001] dconst 1.0000000000000000 => $c0 {DblCns[1.000000]} N002 [000002] mathFN => $c0 {DblCns[1.000000]} N003 [000003] dconst 2.0000000000000000 => $c1 {DblCns[2.000000]} N004 [000004] mathFN => $c2 {DblCns[1.414214]} N005 [000005] + => $c3 {DblCns[2.414214]} N006 [000006] dconst 3.0000000000000000 => $c4 {DblCns[3.000000]} N007 [000007] mathFN => $c5 {DblCns[1.732051]} N008 [000008] + => $c6 {DblCns[4.146264]} N009 [000009] dconst 4.0000000000000000 => $c7 {DblCns[4.000000]} N010 [000010] mathFN => $c1 {DblCns[2.000000]} N011 [000011] + => $c8 {DblCns[6.146264]} N012 [000012] dconst 5.0000000000000000 => $c9 {DblCns[5.000000]} N013 [000013] mathFN => $ca {DblCns[2.236068]} N014 [000014] + => $cb {DblCns[8.382332]} N015 [000015] dconst 6.0000000000000000 => $cc {DblCns[6.000000]} N016 [000016] mathFN => $cd {DblCns[2.449490]} N017 [000017] + => $ce {DblCns[10.831822]} N018 [000018] dconst 7.0000000000000000 => $cf {DblCns[7.000000]} N019 [000019] mathFN => $d0 {DblCns[2.645751]} N020 [000020] + => $d1 {DblCns[13.477573]} ... 41/50 Parallelism
  58. 58. Instruction-level parallelism 42/50 Parallelism
  59. 59. Instruction-level parallelism public void Parallel() { a++; b++; c++; d++; } VS public void Sequential() { a++; a++; a++; a++; } 43/50 Parallelism
  60. 60. Instruction-level parallelism public void Parallel() { a++; b++; c++; d++; } VS public void Sequential() { a++; a++; a++; a++; } LegacyJIT-x86 LegacyJIT-x64 RyuJIT-x64 Parallel 1.2 попугая 1 попугай 1.2 попугая Sequential 6.0 попугая 5.2 попугая 2.2 попугая 43/50 Parallelism
  61. 61. Задачка public void Parallel() { x[0]++; x[1]++; x[2]++; x[3]++; } VS public void Sequential() { x[0]++; x[0]++; x[0]++; x[0]++; } 44/50 Parallelism
  62. 62. Задачка public void Parallel() { x[0]++; x[1]++; x[2]++; x[3]++; } VS public void Sequential() { x[0]++; x[0]++; x[0]++; x[0]++; } LegacyJIT-x86 LegacyJIT-x64 RyuJIT-x64 Parallel 1.2 попугая 1 попугай 1 попугай Sequential 3.3 попугая 2.7 попугая 1 попугай 44/50 Parallelism
  63. 63. Как же так? ; LegacyJIT-x64, Sequential ; x[0]++; mov rdx,qword ptr [rcx+8] mov rax,qword ptr [rdx+8] test rax,rax jbe IndexOutOfRangeException inc dword ptr [rdx+10h] ; x[0]++; mov rdx,qword ptr [rcx+8] mov rax,qword ptr [rdx+8] test rax,rax jbe IndexOutOfRangeException inc dword ptr [rdx+10h] ; ... ; RyuJIT-x64, Sequential ; x[0]++; mov rax,qword ptr [rcx+8] cmp dword ptr [rax+8],0 jbe IndexOutOfRangeException add rax,10h mov rdx,rax mov ecx,dword ptr [rdx] inc ecx mov dword ptr [rdx],ecx ; x[0]++; mov rdx,rax inc ecx mov dword ptr [rdx],ecx ; ... 45/50 Parallelism
  64. 64. История 46/50 Parallelism
  65. 65. Задачка private double[] x = new double[11]; [Benchmark] public double Calc() { double sum = 0.0; for (int i = 1; i < x.Length; i++) sum += 1.0 / (i * i) * x[i]; return sum; } 47/50 Parallelism
  66. 66. Задачка private double[] x = new double[11]; [Benchmark] public double Calc() { double sum = 0.0; for (int i = 1; i < x.Length; i++) sum += 1.0 / (i * i) * x[i]; return sum; } LegacyJIT-x64 RyuJIT-x641 Calc 1 попугай 2 попугая 1 RyuJIT RC 47/50 Parallelism
  67. 67. Как же так? ; LegacyJIT-x64 ; eax = i mov eax,r8d ; eax = i*i imul eax,r8d ; xmm0=i*i cvtsi2sd xmm0,eax ; xmm1=1 movsd xmm1, mmword ptr [7FF9141145E0h] ; xmm1=1/(i*i) divsd xmm1,xmm0 ; xmm1=1/(i*i)*x[i] mulsd xmm1, mmword ptr [rdx+r9+10h] ; xmm1 = sum + 1/(i*i)*x[i] addsd xmm1,xmm2 ; sum = sum + 1/(i*i)*x[i] movapd xmm2,xmm1 ; RyuJIT-x64 ; r8d = i mov r8d,eax ; r8d = i*i imul r8d,eax ; xmm1=i*i vcvtsi2sd xmm1,xmm1,r8d ; xmm2=1 vmovsd xmm2, qword ptr [7FF9140E4398h] ; xmm2=1/(i*i) vdivsd xmm2,xmm2,xmm1 mov r8,rdx movsxd r9,eax ; xmm1 = 1/(i*i) vmovaps xmm1,xmm2 ; xmm1 = 1/(i*i)*x[i] vmulsd xmm1,xmm1, mmword ptr [r8+r9*8+10h] ; sum += 1/(i*i)*x[i] vaddsd xmm0,xmm0,xmm1 См. также: https://github.com/dotnet/coreclr/issues/993 48/50 Parallelism
  68. 68. Методическая литература Для успешных микрооптимизаций нужно очень много знать: 49/50
  69. 69. Вопросы? Андрей Акиньшин, Энтерра http://aakinshin.net https://github.com/AndreyAkinshin https://twitter.com/andrey_akinshin andrey.akinshin@gmail.com 50/50

×