69
Поговорим о микрооптимизациях .NET-приложений Андрей Акиньшин, Энтерра .NEXT 2015 Spb 1/50

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

Embed Size (px)

Citation preview

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

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

Андрей Акиньшин, Энтерра

.NEXT 2015 Spb

1/50

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

Поговорим про общую теорию

Premature optimization is the root of all evil.c©Donald Ervin Knuth

2/50 Theory

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

С чего начать?

Хочу, чтобы программа работала быстрее!

• Хорошая архитектура• Эффективные алгоритмы• Правильные структуры данных• Разумное использование памяти• I/O• Networking• Кэш• ...

Но мы не будем про это разговаривать...

3/50 Theory

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

С чего начать?

Хочу, чтобы программа работала быстрее!• Хорошая архитектура• Эффективные алгоритмы• Правильные структуры данных• Разумное использование памяти• I/O• Networking• Кэш• ...

Но мы не будем про это разговаривать...

3/50 Theory

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

С чего начать?

Хочу, чтобы программа работала быстрее!• Хорошая архитектура• Эффективные алгоритмы• Правильные структуры данных• Разумное использование памяти• I/O• Networking• Кэш• ...

Но мы не будем про это разговаривать...

3/50 Theory

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

Под что будем оптимизировать?

• Версия 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

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

Под что будем оптимизировать?

• Версия 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

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

Микробенчмаркинг — это сложноЧтобы увеличить скорость работы, нужно сначала научиться еёизмерять, а для этого:

• Запускаем бенчмарки в разных процессах.

• Делаем грамотный прогрев и подсчёт статистик.

• Проверяем разные окружения (и не забываем про RyuJIT).

• Побеждаем сайд-эффекты.

• Понимаем устройство .NET, устройство ОС, устройство CPU иустройство вселенной.

На правах рекламы:https://github.com/PerfDotNet/BenchmarkDotNet/https://www.nuget.org/packages/BenchmarkDotNet/c© Andrey Akinshin, Jon Skeet, Matt Warren

Сегодня мы будем проводить измерения в попугаях.

5/50 Theory

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

Микробенчмаркинг — это сложноЧтобы увеличить скорость работы, нужно сначала научиться еёизмерять, а для этого:

• Запускаем бенчмарки в разных процессах.

• Делаем грамотный прогрев и подсчёт статистик.

• Проверяем разные окружения (и не забываем про RyuJIT).

• Побеждаем сайд-эффекты.

• Понимаем устройство .NET, устройство ОС, устройство CPU иустройство вселенной.

На правах рекламы:https://github.com/PerfDotNet/BenchmarkDotNet/https://www.nuget.org/packages/BenchmarkDotNet/c© Andrey Akinshin, Jon Skeet, Matt Warren

Сегодня мы будем проводить измерения в попугаях.

5/50 Theory

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

Микробенчмаркинг — это сложноЧтобы увеличить скорость работы, нужно сначала научиться еёизмерять, а для этого:

• Запускаем бенчмарки в разных процессах.

• Делаем грамотный прогрев и подсчёт статистик.

• Проверяем разные окружения (и не забываем про RyuJIT).

• Побеждаем сайд-эффекты.

• Понимаем устройство .NET, устройство ОС, устройство CPU иустройство вселенной.

На правах рекламы:https://github.com/PerfDotNet/BenchmarkDotNet/https://www.nuget.org/packages/BenchmarkDotNet/c© Andrey Akinshin, Jon Skeet, Matt Warren

Сегодня мы будем проводить измерения в попугаях.

5/50 Theory

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

Микробенчмаркинг — это сложноЧтобы увеличить скорость работы, нужно сначала научиться еёизмерять, а для этого:

• Запускаем бенчмарки в разных процессах.

• Делаем грамотный прогрев и подсчёт статистик.

• Проверяем разные окружения (и не забываем про RyuJIT).

• Побеждаем сайд-эффекты.

• Понимаем устройство .NET, устройство ОС, устройство CPU иустройство вселенной.

На правах рекламы:https://github.com/PerfDotNet/BenchmarkDotNet/https://www.nuget.org/packages/BenchmarkDotNet/c© Andrey Akinshin, Jon Skeet, Matt Warren

Сегодня мы будем проводить измерения в попугаях.

5/50 Theory

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

Микробенчмаркинг — это сложноЧтобы увеличить скорость работы, нужно сначала научиться еёизмерять, а для этого:

• Запускаем бенчмарки в разных процессах.

• Делаем грамотный прогрев и подсчёт статистик.

• Проверяем разные окружения (и не забываем про RyuJIT).

• Побеждаем сайд-эффекты.

• Понимаем устройство .NET, устройство ОС, устройство CPU иустройство вселенной.

На правах рекламы:https://github.com/PerfDotNet/BenchmarkDotNet/https://www.nuget.org/packages/BenchmarkDotNet/c© Andrey Akinshin, Jon Skeet, Matt Warren

Сегодня мы будем проводить измерения в попугаях.

5/50 Theory

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

Поговорим про С#-компилятор и IL

You should always understand at least one layerbelow what you are coding. c©Lee Campbell

6/50 Compiler+IL

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

Поговорим про 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

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

А что внутри?

; Фаза 1: switchIL_0008: switch (IL_001f, IL_0021, IL_0023, IL_0025)IL_001d: br.s IL_0027

; Фаза 2: casesIL_001f: ldc.i4.0IL_0020: retIL_0021: ldc.i4.1IL_0022: retIL_0023: ldc.i4.2IL_0024: retIL_0025: ldc.i4.3IL_0026: retIL_0027: ldc.i4.m1IL_0028: ret

8/50 Compiler+IL

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

Поговорим про 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

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

А что внутри?

; Фаза 1: switchIL_0007: ldloc.0IL_0008: ldc.i4 1000IL_000d: bgt.s IL_001cIL_000f: ldloc.0IL_0010: brfalse.s IL_002eIL_0012: ldloc.0IL_0013: ldc.i4 1000IL_0018: beq.s IL_0030IL_001a: br.s IL_0042IL_001c: ldloc.0IL_001d: ldc.i4 2000IL_0022: beq.s IL_0036IL_0024: ldloc.0IL_0025: ldc.i4 3000IL_002a: beq.s IL_003cIL_002c: br.s IL_0042

; Фаза 2: casesIL_002e: ldc.i4.0IL_002f: retIL_0030: ldc.i4 1000IL_0035: retIL_0036: ldc.i4 2000IL_003b: retIL_003c: ldc.i4 3000IL_0041: retIL_0042: ldc.i4.m1IL_0043: ret

10/50 Compiler+IL

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

Поговорим про switch

switch (x){

case "a":return "A";

case "b":return "B";

case "c":return "C";

case "d":return "D";

}return "";

11/50 Compiler+IL

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

А что внутри?

; Фаза 1: switchIL_0007: ldloc.0IL_0008: ldstr "a"IL_000d: call bool String::op_EqualityIL_0012: brtrue.s IL_003dIL_0014: ldloc.0IL_0015: ldstr "b"IL_001a: call bool String::op_EqualityIL_001f: brtrue.s IL_0043IL_0021: ldloc.0IL_0022: ldstr "c"IL_0027: call bool String::op_EqualityIL_002c: brtrue.s IL_0049IL_002e: ldloc.0IL_002f: ldstr "d"IL_0034: call bool String::op_EqualityIL_0039: brtrue.s IL_004fIL_003b: br.s IL_0055

; Фаза 2: casesIL_003d: ldstr "A"IL_0042: retIL_0043: ldstr "B"IL_0048: retIL_0049: ldstr "C"IL_004e: retIL_004f: ldstr "D"IL_0054: retIL_0055: ldstr ""IL_005a: ret

12/50 Compiler+IL

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

Поговорим про switchswitch (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

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

А что внутри?; Старый компилятор; Фаза 0: Dictionary<string, string>IL_000d: volatile.IL_000f: ldsfld class

Dictionary<string, int32>IL_0014: brtrue.s IL_0077IL_0016: ldc.i4.7IL_0017: newobj instance void class

Dictionary<string, int32>::.ctorIL_001c: dupIL_001d: ldstr "a"IL_0022: ldc.i4.0IL_0023: call instance void class

Dictionary<string, int32>::AddIL_0028: dupIL_0029: ldstr "b"IL_002e: ldc.i4.1IL_0023: call instance void classDictionary<string, int32>::AddIL_0034: dupIL_0035: ldstr "c"; ...

; Фаза 1:IL_0088: ldloc.1IL_0089: switch (

IL_00ac, IL_00b2, IL_00b8,IL_00be, IL_00c4, IL_00ca, IL_00d0)

IL_00aa: br.s IL_00d6; Фаза 2: casesIL_00ac: ldstr "A"IL_00b1: retIL_00b2: ldstr "B"IL_00b7: retIL_00b8: ldstr "C"IL_00bd: retIL_00be: ldstr "D"IL_00c3: retIL_00c4: ldstr "E"IL_00c9: retIL_00ca: ldstr "F"IL_00cf: retIL_00d0: ldstr "G"IL_00d5: retIL_00d6: ldstr ""IL_00db: ret

14/50 Compiler+IL

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

А что внутри?

// Roslyn// Фаза 1: ComputeStringHashuint 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

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

История

16/50 Compiler+IL

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

Поговорим про Readonly fieldspublic 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-x64GetValue 1 попугай 1 попугайGetReadOnlyValue 6.2 попугая 7.6 попугая

17/50 Compiler+IL

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

Поговорим про Readonly fieldspublic 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-x64GetValue 1 попугай 1 попугайGetReadOnlyValue 6.2 попугая 7.6 попугая

17/50 Compiler+IL

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

Как же так?

; GetValueIL_0000: ldarg.0IL_0001: ldflda valuetype Program::aIL_0006: call instance int64 Int256::get_Bits0()

; GetReadOnlyValueIL_0000: ldarg.0IL_0001: ldfld valuetype Program::bIL_0006: stloc.0IL_0007: ldloca.s 0IL_0009: call instance int64 Int256::get_Bits0()

См. также: Jon Skeet, Micro-optimization: the surprising inefficiency of readonly fields

18/50 Compiler+IL

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

Поговорим про JIT и ASM

19/50 JIT+ASM

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

Наши новые друзья

• 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

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

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-x64N 1.3 попугая 1 попугай 1.6 попугаяa.Length 1.2 попугая 1 попугай 1.5 попугая

21/50 JIT+ASM

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

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-x64N 1.3 попугая 1 попугай 1.6 попугаяa.Length 1.2 попугая 1 попугай 1.5 попугая

21/50 JIT+ASM

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

Как же так?

LegacyJIT-x86; LegacyJIT-x86, N; ...Loop:; if i >= a.Lengthcmp eax,edx; then throw Exceptionjae 004635F9; sum += a[i]add ecx,dword ptr [esi+eax*4+8]; i++inc eax; if i < Ncmp eax,0Bh; then continuejl Loop

; LegacyJIT-x86, a.Length; ...; ...; ...; ...; ...Loop:; sum += a[i]add ecx,dword ptr [esi+edx*4+8]; i++inc edx; if i < a.Lengthcmp eax,edx; then continuejg Loop

22/50 JIT+ASM

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

Как же так?

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 < Ncmp rcx,2Ch; then continuejl Loop

; LegacyJIT-x64, a.Length; ...Loop:; eax = x[i]mov eax,dword ptr [r9+r8+10h]; sum += a[i]add ecx,eax; i++inc edxadd r8,4; if i < Ncmp edx,r10d; then continuejl Loop

23/50 JIT+ASM

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

Как же так?

RyuJIT-x64; RyuJIT-x64, NLoop:; r8 = a.Lengthmov r8d,dword ptr [rax+8]; if i >= a.Lengthcmp ecx,r8d; then throw exceptionjae 00007FF94F9B46B4; r8 = imovsxd r8,ecx; r8 = a[i]mov r8d,dword ptr [rax+r8*4+10h]; sum += a[i]add edx,r8d; i++inc ecx; if i < Ncmp ecx,0Bh; then continuejl Loop

; RyuJIT-x64, a.Length; ...; ...; ...; ...; ...; ...Loop:; r9 = imovsxd 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.Lengthcmp r8d,ecx; then continuejg Loop

24/50 JIT+ASM

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

История

25/50 JIT+ASM

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

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

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

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-x641 попугай 0 попугаев 1 попугай

27/50 JIT+ASM

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

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-x641 попугай 0 попугаев 1 попугай

27/50 JIT+ASM

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

Как же так?

LegacyJIT-x64

; LegacyJIT-x64mov ecx,23hret

RyuJIT-x64

// Inline expansion aborted due to opcode// [06] OP_starg.s in method// Program:WithStarg(int):int:this

28/50 JIT+ASM

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

Как же так?

LegacyJIT-x64

; LegacyJIT-x64mov ecx,23hret

RyuJIT-x64

// Inline expansion aborted due to opcode// [06] OP_starg.s in method// Program:WithStarg(int):int:this

28/50 JIT+ASM

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

История

29/50 JIT+ASM

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

Loop unrolling

int sum = 0;for (int i = 0; i < 1024; i++)

sum += i;

LegacyJIT-x86 LegacyJIT-x64 RyuJIT-x641.0 попугай 0.9 попугая 1.0 попугай

30/50 JIT+ASM

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

Loop unrolling

int sum = 0;for (int i = 0; i < 1024; i++)

sum += i;

LegacyJIT-x86 LegacyJIT-x64 RyuJIT-x641.0 попугай 0.9 попугая 1.0 попугай

30/50 JIT+ASM

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

Как же так?

LegacyJIT-x64

xor ecx,ecxmov edx,1Loop: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 += 4cmp edx,401hjl Loop

31/50 JIT+ASM

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

Поговорим про параллелизм

Но не будем разговаривать про потоки, задачи,PLINQ и прочий мультитрединг...

32/50 Parallelism

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

Поговорим про параллелизм

Но не будем разговаривать про потоки, задачи,PLINQ и прочий мультитрединг...

32/50 Parallelism

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

Поговорим про SIMDprivate 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-x64MyMul 34 попугая 5 попугаевBclMul 34 попугая 1 попугай

33/50 Parallelism

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

Поговорим про SIMDprivate 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-x64MyMul 34 попугая 5 попугаевBclMul 34 попугая 1 попугай

33/50 Parallelism

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

Как же так?

; 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,eaxmov qword ptr [rsp],raxmov qword ptr [rsp+8],raxlea rax,[rsp]movss dword ptr [rax],xmm3movss dword ptr [rax+4],xmm2movss dword ptr [rax+8],xmm1movss dword ptr [rax+0Ch],xmm0; ...

; RyuJIT-x64

; MyMul; ...vmulss xmm0,xmm0,xmm4vmulss xmm1,xmm1,xmm5vmulss xmm2,xmm2,xmm6vmulss xmm3,xmm3,xmm7; ...

; BclMulvmovupd xmm0,xmmword ptr [rcx+8]vmovupd xmm1,xmmword ptr [rcx+18h]vmulps xmm0,xmm0,xmm1vmovupd xmmword ptr [rcx+28h],xmm0

34/50 Parallelism

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

Думаем про 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 vaddsdx * 2 fmul mulsd vmulsdSqrt(X) fsqrt sqrtsd vsqrtsd

35/50 Parallelism

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

Думаем про 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 vaddsdx * 2 fmul mulsd vmulsdSqrt(X) fsqrt sqrtsd vsqrtsd

35/50 Parallelism

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

История

36/50 Parallelism

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

Задачка

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 попугай

37/50 Parallelism

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

Задачка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 попугай

1RyuJIT RC37/50 Parallelism

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

Как же так?

RyuJIT-x64, Sqrt13vsqrtsd xmm0,xmm0,mmword ptr [7FF94F9E4D28h]vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D30h]vaddsd xmm0,xmm0,xmm1vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D38h]vaddsd xmm0,xmm0,xmm1vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D40h]vaddsd xmm0,xmm0,xmm1vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D48h]vaddsd xmm0,xmm0,xmm1vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D50h]vaddsd xmm0,xmm0,xmm1vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D58h]vaddsd xmm0,xmm0,xmm1vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D60h]vaddsd xmm0,xmm0,xmm1vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D68h]vaddsd xmm0,xmm0,xmm1vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D70h]vaddsd xmm0,xmm0,xmm1vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D78h]vaddsd xmm0,xmm0,xmm1vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D80h]vaddsd xmm0,xmm0,xmm1vsqrtsd xmm1,xmm0,mmword ptr [7FF94F9E4D88h]vaddsd xmm0,xmm0,xmm1ret

38/50 Parallelism

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

Как же так?

RyuJIT-x64, Sqrt14

vmovsd xmm0,qword ptr [7FF94F9C4C80h]ret

39/50 Parallelism

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

Как же так?Большое дерево выражения

* 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

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

Как же так?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

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

Instruction-level parallelism

42/50 Parallelism

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

Instruction-level parallelismpublic void Parallel(){

a++; b++; c++; d++;}

VS

public void Sequential(){

a++; a++; a++; a++;}

LegacyJIT-x86 LegacyJIT-x64 RyuJIT-x64Parallel 1.2 попугая 1 попугай 1.2 попугаяSequential 6.0 попугая 5.2 попугая 2.2 попугая

43/50 Parallelism

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

Instruction-level parallelismpublic void Parallel(){

a++; b++; c++; d++;}

VS

public void Sequential(){

a++; a++; a++; a++;}

LegacyJIT-x86 LegacyJIT-x64 RyuJIT-x64Parallel 1.2 попугая 1 попугай 1.2 попугаяSequential 6.0 попугая 5.2 попугая 2.2 попугая

43/50 Parallelism

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

Задачка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-x64Parallel 1.2 попугая 1 попугай 1 попугайSequential 3.3 попугая 2.7 попугая 1 попугай

44/50 Parallelism

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

Задачка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-x64Parallel 1.2 попугая 1 попугай 1 попугайSequential 3.3 попугая 2.7 попугая 1 попугай

44/50 Parallelism

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

Как же так?

; LegacyJIT-x64, Sequential; x[0]++;mov rdx,qword ptr [rcx+8]mov rax,qword ptr [rdx+8]test rax,raxjbe IndexOutOfRangeExceptioninc dword ptr [rdx+10h]

; x[0]++;mov rdx,qword ptr [rcx+8]mov rax,qword ptr [rdx+8]test rax,raxjbe IndexOutOfRangeExceptioninc dword ptr [rdx+10h]

; ...

; RyuJIT-x64, Sequential; x[0]++;mov rax,qword ptr [rcx+8]cmp dword ptr [rax+8],0jbe IndexOutOfRangeExceptionadd rax,10hmov rdx,raxmov ecx,dword ptr [rdx]inc ecxmov dword ptr [rdx],ecx

; x[0]++;mov rdx,raxinc ecxmov dword ptr [rdx],ecx

; ...

45/50 Parallelism

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

История

46/50 Parallelism

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

Задачка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 попугая

47/50 Parallelism

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

Задачка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 попугая

1RyuJIT RC47/50 Parallelism

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

Как же так?; LegacyJIT-x64; eax = imov eax,r8d; eax = i*iimul eax,r8d; xmm0=i*icvtsi2sd xmm0,eax; xmm1=1movsd 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 = imov r8d,eax; r8d = i*iimul r8d,eax; xmm1=i*ivcvtsi2sd xmm1,xmm1,r8d; xmm2=1vmovsd xmm2,

qword ptr [7FF9140E4398h]; xmm2=1/(i*i)vdivsd xmm2,xmm2,xmm1mov r8,rdxmovsxd 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

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

Методическая литератураДля успешных микрооптимизаций нужно очень много знать:

49/50

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

Вопросы?

Андрей Акиньшин, Энтерраhttp://aakinshin.net

https://github.com/AndreyAkinshinhttps://twitter.com/andrey_akinshin

[email protected]

50/50