SlideShare une entreprise Scribd logo
1  sur  64
Télécharger pour lire hors ligne
Распространённые ошибки оценки производительности
.NET-приложений
Андрей Акиньшин, JetBrains
SPB .NET Meetup #6
1/48
Часть 1
Теория
2/48 Теория
Поговорим про бенчмарки
c Wikipedia
c Aleksey Shipil¨ev
3/48 Теория
План работ
1 Поставить задачу
2 Выбрать метрики
3 Выбрать инструмент
4 Провести эксперимент
5 Выполнить анализ, сделать выводы
4/48 Теория
Окружение
• C#-компилятор: версия старого csc? Roslyn?
• Версия CLR: CLR2? CLR4? CoreCLR? Mono?
• Версия ОС: Windows? Linux? MacOS? FreeBSD?
• Версия JIT: x86? x64? RyuJIT?
• Версия GC: MS (какой CLR?)? Mono (Boehm/Sgen)?
• Компиляция: JIT? NGen? .NET Native?
• Железо: ???
• ...
5/48 Теория
Запуск бенчмарка
• Release build
• Без дебаггера
• Выключите другие приложения
• Используйте максимальную
производительность
6/48 Теория
DateTime vs Stopwatch
var start = DateTime.Now;
Foo();
var finish = DateTime.Now;
Console.WriteLine((finish - start).Milliseconds);
vs
var sw = Stopwatch.StartNew();
Foo();
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
7/48 Теория
DateTime vs Stopwatch
var start = DateTime.Now;
Foo();
var finish = DateTime.Now;
Console.WriteLine((finish - start).Milliseconds);
vs
var sw = Stopwatch.StartNew();
Foo();
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
Возможные значения (Windows 10, MS.NET, Core i7):
Granularity Latency
DateTime 1 000 000 ns∗
30–40 ns
Stopwatch 370–466 ns 14–18 ns
7/48 Теория
Итерации
Плохо:
// Granularity(Stopwatch) = 466 ns
// Latency(Stopwatch) = 18 ns
var sw = Stopwatch.StartNew();
Foo(); // 100 ns
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
8/48 Теория
Итерации
Плохо:
// Granularity(Stopwatch) = 466 ns
// Latency(Stopwatch) = 18 ns
var sw = Stopwatch.StartNew();
Foo(); // 100 ns
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
Лучше:
var sw = Stopwatch.StartNew();
for (int i = 0; i < N; i++) // (N * 100 + eps) ns
Foo();
sw.Stop();
var total = sw.ElapsedTicks / Stopwatch.Frequency;
Console.WriteLine(total / N);
8/48 Теория
Прогрев
Запустим бенчмарк несколько раз:
int[] x = new int[128 * 1024 * 1024];
for (int iter = 0; iter < 5; iter++)
{
var sw = Stopwatch.StartNew();
for (int i = 0; i < x.Length; i += 16)
x[i]++;
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
9/48 Теория
Прогрев
Запустим бенчмарк несколько раз:
int[] x = new int[128 * 1024 * 1024];
for (int iter = 0; iter < 5; iter++)
{
var sw = Stopwatch.StartNew();
for (int i = 0; i < x.Length; i += 16)
x[i]++;
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
Результат:
176
81
62
62
62
9/48 Теория
Несколько запусков бенчмарка
Run 01 : 529.8674 ns/op
Run 02 : 532.7541 ns/op
Run 03 : 558.7448 ns/op
Run 04 : 555.6647 ns/op
Run 05 : 539.6401 ns/op
Run 06 : 539.3494 ns/op
Run 07 : 564.3222 ns/op
Run 08 : 551.9544 ns/op
Run 09 : 550.1608 ns/op
Run 10 : 533.0634 ns/op
10/48 Теория
Несколько запусков CLR
11/48 Теория
Статистика
12/48 Теория
Накладные расходы
var sw = Stopwatch.StartNew();
int x = 0;
for (int i = 0; i < N; i++) // overhead
x++; // target operation
sw.Stop();
13/48 Теория
Изоляция бенчмарков
Плохо:
Measure1();
Measure2();
14/48 Теория
Изоляция бенчмарков
Плохо:
Measure1();
Measure2();
Вспомним про:
• Interface method dispatch
• Garbage collector
14/48 Теория
Борьба с оптимизациями
• Dead code elimination
• Inlining
• Constant folding
• Instruction Level Parallelism
• Branch prediction
• ...
15/48 Теория
Организация доступа к памяти
Event Latency Scaled
1 CPU cycle 0.3 ns 1 s
Level 1 cache access 0.9 ns 3 s
Level 2 cache access 2.8 ns 9 s
Level 3 cache access 12.9 ns 43 s
Main memory access 120 ns 6 min
Solid-state disk I/O 50-150 µs 2-6 days
Rotational disk I/O 1-10 ms 1-12 months
Internet: SF to NYC 40 ms 4 years
Internet: SF to UK 81 ms 8 years
Internet: SF to Australia 183 ms 19 years
OS virtualization reboot 4 s 423 years
SCSI command time-out 30 s 3000 years
Hardware virtualization reboot 40 s 4000 years
Physical system reboot 5 m 32 millenia
c Systems Performance: Enterprise and the Cloud
16/48 Теория
Processor affinity
17/48 Теория
Многопоточные бенчмарки сложны
False sharing:
c http://colfaxresearch.com/optimization-techniques-for-the-intel-mic-architecture-
part-3-of-3-false-sharing-and-padding/
18/48 Теория
False sharing в действии
private static int[] x = new int[1024];
private void Inc(int p)
{
for (int i = 0; i < 10000001; i++)
x[p]++;
}
private void Run(int step)
{
var sw = Stopwatch.StartNew();
Task.WaitAll(
Task.Factory.StartNew(() => Inc(0 * step)),
Task.Factory.StartNew(() => Inc(1 * step)),
Task.Factory.StartNew(() => Inc(2 * step)),
Task.Factory.StartNew(() => Inc(3 * step)));
Console.WriteLine(sw.ElapsedMilliseconds);
}
19/48 Теория
False sharing в действии
private static int[] x = new int[1024];
private void Inc(int p)
{
for (int i = 0; i < 10000001; i++)
x[p]++;
}
private void Run(int step)
{
var sw = Stopwatch.StartNew();
Task.WaitAll(
Task.Factory.StartNew(() => Inc(0 * step)),
Task.Factory.StartNew(() => Inc(1 * step)),
Task.Factory.StartNew(() => Inc(2 * step)),
Task.Factory.StartNew(() => Inc(3 * step)));
Console.WriteLine(sw.ElapsedMilliseconds);
}
Run(1) Run(256)
∼400 ∼150
19/48 Теория
BenchmarkDotNet
20/48 Теория
BenchmarkDotNet
v0.7.8:
• Создание отдельного проекта для каждого бенчмарка
• Запуск под разными окружениями
• Прогрев, многократный запуск, статистики
• Анализ накладных расходов
• И много чего ещё...
21/48 Теория
BenchmarkDotNet
v0.7.8:
• Создание отдельного проекта для каждого бенчмарка
• Запуск под разными окружениями
• Прогрев, многократный запуск, статистики
• Анализ накладных расходов
• И много чего ещё...
В следующих сериях:
• Просмотр IL и ASM
• Графики
• Поддержка CoreCLR/.NET Native
• Многопоточные бенчмарки
21/48 Теория
Часть 2
Практика
22/48 Практика
Сумма элементов массива
const int N = 1024;
int[,] a = new int[N, N];
[Benchmark]
public double SumIj()
{
var sum = 0;
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
sum += a[i, j];
return sum;
}
[Benchmark]
public double SumJi()
{
var sum = 0;
for (int j = 0; j < N; j++)
for (int i = 0; i < N; i++)
sum += a[i, j];
return sum;
}
23/48 Практика
Сумма элементов массива
const int N = 1024;
int[,] a = new int[N, N];
[Benchmark]
public double SumIj()
{
var sum = 0;
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
sum += a[i, j];
return sum;
}
[Benchmark]
public double SumJi()
{
var sum = 0;
for (int j = 0; j < N; j++)
for (int i = 0; i < N; i++)
sum += a[i, j];
return sum;
}
SumIj SumJi
LegacyJIT-x86 1 попугай 3.5 попугая
23/48 Практика
Структуры данных
Binary Search
24/48 Практика
Структуры данных
Cache-Conscious Binary Search
25/48 Практика
Branch prediction
const int N = 32767;
int[] sorted, unsorted; // random numbers [0..255]
private static int Sum(int[] data)
{
int sum = 0;
for (int i = 0; i < N; i++)
if (data[i] >= 128)
sum += data[i];
return sum;
}
[Benchmark]
public int Sorted()
{
return Sum(sorted);
}
[Benchmark]
public int Unsorted()
{
return Sum(unsorted);
}
26/48 Практика
Branch prediction
const int N = 32767;
int[] sorted, unsorted; // random numbers [0..255]
private static int Sum(int[] data)
{
int sum = 0;
for (int i = 0; i < N; i++)
if (data[i] >= 128)
sum += data[i];
return sum;
}
[Benchmark]
public int Sorted()
{
return Sum(sorted);
}
[Benchmark]
public int Unsorted()
{
return Sum(unsorted);
}
Sorted Unsorted
LegacyJIT-x86 1 попугай 7.4 попугая
26/48 Практика
Интерфейсы
private interface IFoo {
double Inc(double x);
}
private class Foo1 : IFoo {
public double Inc(double x) =>
x + 1;
}
private class Foo2 : IFoo {
public double Inc(double x) =>
x + 1;
}
private double Run(IFoo foo) {
double sum = 0;
for (int i = 0; i < 1001; i++)
sum += foo.Inc(0);
return sum;
}
[Benchmark]
public double Run11() {
var bar1 = new Foo1();
var bar2 = new Foo1();
return Run(bar1) + Run(bar2);
}
[Benchmark]
public double Run12() {
var bar1 = new Foo1();
var bar2 = new Foo2();
return Run(bar1) + Run(bar2);
}
27/48 Практика
Интерфейсы
private interface IFoo {
double Inc(double x);
}
private class Foo1 : IFoo {
public double Inc(double x) =>
x + 1;
}
private class Foo2 : IFoo {
public double Inc(double x) =>
x + 1;
}
private double Run(IFoo foo) {
double sum = 0;
for (int i = 0; i < 1001; i++)
sum += foo.Inc(0);
return sum;
}
[Benchmark]
public double Run11() {
var bar1 = new Foo1();
var bar2 = new Foo1();
return Run(bar1) + Run(bar2);
}
[Benchmark]
public double Run12() {
var bar1 = new Foo1();
var bar2 = new Foo2();
return Run(bar1) + Run(bar2);
}
Run11 Run12
LegacyJIT-x64 1 попугай 1.25 попугая
27/48 Практика
История
28/48 Практика
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;
}
29/48 Практика
Inlining
[Benchmark]
int Calc() => WithoutStarg(0x11) + WithStarg(0x12);
int WithoutStarg(int value) => value;
int WithStarg(int value)
{
if (value < 0)
value = -value;
return value;
}
30/48 Практика
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 попугай
30/48 Практика
Как же так?
LegacyJIT-x64
; LegacyJIT-x64
mov ecx,23h
ret
31/48 Практика
Как же так?
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
31/48 Практика
История
32/48 Практика
Поговорим про 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;
33/48 Практика
Поговорим про 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 попугая
33/48 Практика
Как же так?
; 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
34/48 Практика
Поговорим про 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;
35/48 Практика
Поговорим про 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 попугай
35/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
36/48 Практика
Думаем про ASM
double x = /* ... */ ;
double a = x + 1;
double b = x * 2;
double c = Math.Sqrt(x);
37/48 Практика
Думаем про 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
37/48 Практика
История
38/48 Практика
Задачка
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);
39/48 Практика
Задачка
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
39/48 Практика
Как же так?
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
40/48 Практика
Как же так?
RyuJIT-x64, Sqrt14
vmovsd xmm0,qword ptr [7FF94F9C4C80h]
ret
41/48 Практика
Как же так?
Большое дерево выражения
* 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
// ...
42/48 Практика
Как же так?
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]}
...
43/48 Практика
История
44/48 Практика
Задачка
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;
}
45/48 Практика
Задачка
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
45/48 Практика
Как же так?
; 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
46/48 Практика
Методическая литература
Для успешных микрооптимизаций нужно очень много знать:
47/48
Вопросы?
Андрей Акиньшин
http://aakinshin.net
https://github.com/AndreyAkinshin
https://twitter.com/andrey_akinshin
andrey.akinshin@gmail.com
48/48

Contenu connexe

Tendances

Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building BlocksЛекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
Mikhail Kurnosov
 
разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3
Eugeniy Tyumentcev
 

Tendances (19)

Алексей Куканов — Параллелизм в C++: управляйте приложением, а не потоками!
Алексей Куканов — Параллелизм в C++: управляйте приложением, а не потоками!Алексей Куканов — Параллелизм в C++: управляйте приложением, а не потоками!
Алексей Куканов — Параллелизм в C++: управляйте приложением, а не потоками!
 
Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»
Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»
Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»
 
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building BlocksЛекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
 
C++ CoreHard Autumn 2018. Полезный constexpr - Антон Полухин
C++ CoreHard Autumn 2018. Полезный constexpr - Антон ПолухинC++ CoreHard Autumn 2018. Полезный constexpr - Антон Полухин
C++ CoreHard Autumn 2018. Полезный constexpr - Антон Полухин
 
Сверхоптимизация кода на Python
Сверхоптимизация кода на PythonСверхоптимизация кода на Python
Сверхоптимизация кода на Python
 
Максим Хижинский Lock-free maps
Максим Хижинский Lock-free mapsМаксим Хижинский Lock-free maps
Максим Хижинский Lock-free maps
 
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
 
Лекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building BlocksЛекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building Blocks
 
Продолжаем говорить про арифметику
Продолжаем говорить про арифметикуПродолжаем говорить про арифметику
Продолжаем говорить про арифметику
 
Антон Полухин, Немного о Boost
Антон Полухин, Немного о BoostАнтон Полухин, Немного о Boost
Антон Полухин, Немного о Boost
 
ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...
ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...
ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...
 
ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. М...
ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. М...ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. М...
ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. М...
 
ПВТ - весна 2015 - Лекция 3. Реентерабельность. Сигналы. Локальные данные пот...
ПВТ - весна 2015 - Лекция 3. Реентерабельность. Сигналы. Локальные данные пот...ПВТ - весна 2015 - Лекция 3. Реентерабельность. Сигналы. Локальные данные пот...
ПВТ - весна 2015 - Лекция 3. Реентерабельность. Сигналы. Локальные данные пот...
 
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
 
Григорий Демченко, “Асинхронность и сопрограммы: обработка данных“
Григорий Демченко, “Асинхронность и сопрограммы: обработка данных“Григорий Демченко, “Асинхронность и сопрограммы: обработка данных“
Григорий Демченко, “Асинхронность и сопрограммы: обработка данных“
 
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионаловПолухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
 
CPU Performance in Java.
CPU Performance in Java.CPU Performance in Java.
CPU Performance in Java.
 
разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3
 
разработка серверов и серверных приложений лекция №2
разработка серверов и серверных приложений лекция №2разработка серверов и серверных приложений лекция №2
разработка серверов и серверных приложений лекция №2
 

En vedette

En vedette (17)

Intertextuality
IntertextualityIntertextuality
Intertextuality
 
Misery
MiseryMisery
Misery
 
Orphan
OrphanOrphan
Orphan
 
Hannibal
HannibalHannibal
Hannibal
 
Record labels
Record labelsRecord labels
Record labels
 
Многопоточность в .NET: Дьявол в деталях
Многопоточность в .NET: Дьявол в деталяхМногопоточность в .NET: Дьявол в деталях
Многопоточность в .NET: Дьявол в деталях
 
LSPR Intro to Advert chapter 3
LSPR Intro to Advert chapter 3LSPR Intro to Advert chapter 3
LSPR Intro to Advert chapter 3
 
LSPR Intro to Advert chapter 4&5
LSPR Intro to Advert chapter 4&5LSPR Intro to Advert chapter 4&5
LSPR Intro to Advert chapter 4&5
 
LSPR Intro to Advert chapter 2
LSPR Intro to Advert chapter 2LSPR Intro to Advert chapter 2
LSPR Intro to Advert chapter 2
 
LSPR Intro to Advert chapter 1
LSPR Intro to Advert chapter 1LSPR Intro to Advert chapter 1
LSPR Intro to Advert chapter 1
 
Sandboxing in .NET CLR
Sandboxing in .NET CLRSandboxing in .NET CLR
Sandboxing in .NET CLR
 
Katy perry roar analysis
Katy perry roar analysisKaty perry roar analysis
Katy perry roar analysis
 
Digipak analysis
Digipak analysisDigipak analysis
Digipak analysis
 
The sixth sense
The sixth senseThe sixth sense
The sixth sense
 
Shutter Island Audience Analysis
Shutter Island Audience AnalysisShutter Island Audience Analysis
Shutter Island Audience Analysis
 
The da vinci code
The da vinci codeThe da vinci code
The da vinci code
 
Знакомство с In-Memory Data Grid
Знакомство с In-Memory Data GridЗнакомство с In-Memory Data Grid
Знакомство с In-Memory Data Grid
 

Similaire à Распространённые ошибки оценки производительности .NET-приложений

статический анализ кода
статический анализ кодастатический анализ кода
статический анализ кода
Andrey Karpov
 
Всё о статическом анализе кода для Java программиста
Всё о статическом анализе кода для Java программистаВсё о статическом анализе кода для Java программиста
Всё о статическом анализе кода для Java программиста
Andrey Karpov
 
Опыт разработки статического анализатора кода
Опыт разработки статического анализатора кодаОпыт разработки статического анализатора кода
Опыт разработки статического анализатора кода
Andrey Karpov
 
Юнит-тестирование и Google Mock. Влад Лосев, Google
Юнит-тестирование и Google Mock. Влад Лосев, GoogleЮнит-тестирование и Google Mock. Влад Лосев, Google
Юнит-тестирование и Google Mock. Влад Лосев, Google
yaevents
 
Tech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVMTech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU
 

Similaire à Распространённые ошибки оценки производительности .NET-приложений (20)

статический анализ кода
статический анализ кодастатический анализ кода
статический анализ кода
 
Статический анализ кода
Статический анализ кода Статический анализ кода
Статический анализ кода
 
Erlang
ErlangErlang
Erlang
 
Введение в разработку многопоточных приложений
Введение в разработку многопоточных приложенийВведение в разработку многопоточных приложений
Введение в разработку многопоточных приложений
 
Progr labrab-4-2013-c++
Progr labrab-4-2013-c++Progr labrab-4-2013-c++
Progr labrab-4-2013-c++
 
Parallel STL
Parallel STLParallel STL
Parallel STL
 
Сверхоптимизация кода на Python
Сверхоптимизация кода на PythonСверхоптимизация кода на Python
Сверхоптимизация кода на Python
 
Всё о статическом анализе кода для Java программиста
Всё о статическом анализе кода для Java программистаВсё о статическом анализе кода для Java программиста
Всё о статическом анализе кода для Java программиста
 
JPoint 2016 - Etudes of DIY Java profiler
JPoint 2016 - Etudes of DIY Java profilerJPoint 2016 - Etudes of DIY Java profiler
JPoint 2016 - Etudes of DIY Java profiler
 
Когда в C# не хватает C++ . Часть 3.
Когда в C# не хватает C++. Часть 3. Когда в C# не хватает C++. Часть 3.
Когда в C# не хватает C++ . Часть 3.
 
Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до ЯБыстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
 
Оптимизация производительности Python
Оптимизация производительности PythonОптимизация производительности Python
Оптимизация производительности Python
 
Опыт разработки статического анализатора кода
Опыт разработки статического анализатора кодаОпыт разработки статического анализатора кода
Опыт разработки статического анализатора кода
 
Статический и динамический полиморфизм в C++, Дмитрий Леванов
Статический и динамический полиморфизм в C++, Дмитрий ЛевановСтатический и динамический полиморфизм в C++, Дмитрий Леванов
Статический и динамический полиморфизм в C++, Дмитрий Леванов
 
Опыт применения активных объектов во встраиваемых системах. Архитектурные асп...
Опыт применения активных объектов во встраиваемых системах. Архитектурные асп...Опыт применения активных объектов во встраиваемых системах. Архитектурные асп...
Опыт применения активных объектов во встраиваемых системах. Архитектурные асп...
 
Aleksei Milovidov "Let's optimize one aggregate function in ClickHouse"
Aleksei Milovidov "Let's optimize one aggregate function in ClickHouse"Aleksei Milovidov "Let's optimize one aggregate function in ClickHouse"
Aleksei Milovidov "Let's optimize one aggregate function in ClickHouse"
 
Юнит-тестирование и Google Mock. Влад Лосев, Google
Юнит-тестирование и Google Mock. Влад Лосев, GoogleЮнит-тестирование и Google Mock. Влад Лосев, Google
Юнит-тестирование и Google Mock. Влад Лосев, Google
 
Статический анализ: ищем ошибки... и уязвимости?
Статический анализ: ищем ошибки... и уязвимости?Статический анализ: ищем ошибки... и уязвимости?
Статический анализ: ищем ошибки... и уязвимости?
 
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
 
Tech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVMTech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVM
 

Plus de Mikhail Shcherbakov

Plus de Mikhail Shcherbakov (20)

Delegates and events in C#
Delegates and events in C#Delegates and events in C#
Delegates and events in C#
 
Mythbusters - Web Application Security
Mythbusters - Web Application SecurityMythbusters - Web Application Security
Mythbusters - Web Application Security
 
Михаил Щербаков "WinDbg сотоварищи"
Михаил Щербаков "WinDbg сотоварищи"Михаил Щербаков "WinDbg сотоварищи"
Михаил Щербаков "WinDbg сотоварищи"
 
Apache Ignite.NET в действии
Apache Ignite.NET в действииApache Ignite.NET в действии
Apache Ignite.NET в действии
 
Архитектура Apache Ignite .NET
Архитектура Apache Ignite .NETАрхитектура Apache Ignite .NET
Архитектура Apache Ignite .NET
 
сценарии использования статического анализатора
сценарии использования статического анализаторасценарии использования статического анализатора
сценарии использования статического анализатора
 
WCF. Легко или проблемно
WCF. Легко или проблемноWCF. Легко или проблемно
WCF. Легко или проблемно
 
Поиск ошибок в программах на языке C#
Поиск ошибок в программах на языке C#Поиск ошибок в программах на языке C#
Поиск ошибок в программах на языке C#
 
Project Rider
Project RiderProject Rider
Project Rider
 
WinDbg в руках .NET разработчика
WinDbg в руках .NET разработчикаWinDbg в руках .NET разработчика
WinDbg в руках .NET разработчика
 
Structured logging
Structured loggingStructured logging
Structured logging
 
RESTful API: Best practices, versioning, design documentation
RESTful API: Best practices, versioning, design documentationRESTful API: Best practices, versioning, design documentation
RESTful API: Best practices, versioning, design documentation
 
Простой и кросс-платформенный WEB-сервер на .NET
Простой и кросс-платформенный WEB-сервер на .NETПростой и кросс-платформенный WEB-сервер на .NET
Простой и кросс-платформенный WEB-сервер на .NET
 
Использование Visual Studio Tools for Apache Cordova в реальных проектах
Использование Visual Studio Tools for Apache Cordova в реальных проектахИспользование Visual Studio Tools for Apache Cordova в реальных проектах
Использование Visual Studio Tools for Apache Cordova в реальных проектах
 
Когда в C# не хватает C++ . Часть 2.
Когда в C# не хватает C++. Часть 2.Когда в C# не хватает C++. Часть 2.
Когда в C# не хватает C++ . Часть 2.
 
Когда в C# не хватает C++
Когда в C# не хватает C++Когда в C# не хватает C++
Когда в C# не хватает C++
 
Как это работает: DLR
Как это работает: DLRКак это работает: DLR
Как это работает: DLR
 
Visual Studio 2015 Diagnostic and Debugging Tools
Visual Studio 2015 Diagnostic and Debugging ToolsVisual Studio 2015 Diagnostic and Debugging Tools
Visual Studio 2015 Diagnostic and Debugging Tools
 
Разработка мобильных приложений на С# с использованием Xamarin
Разработка мобильных  приложений на С# с использованием XamarinРазработка мобильных  приложений на С# с использованием Xamarin
Разработка мобильных приложений на С# с использованием Xamarin
 
Roslyn Code Analysis
Roslyn Code AnalysisRoslyn Code Analysis
Roslyn Code Analysis
 

Распространённые ошибки оценки производительности .NET-приложений

  • 1. Распространённые ошибки оценки производительности .NET-приложений Андрей Акиньшин, JetBrains SPB .NET Meetup #6 1/48
  • 3. Поговорим про бенчмарки c Wikipedia c Aleksey Shipil¨ev 3/48 Теория
  • 4. План работ 1 Поставить задачу 2 Выбрать метрики 3 Выбрать инструмент 4 Провести эксперимент 5 Выполнить анализ, сделать выводы 4/48 Теория
  • 5. Окружение • C#-компилятор: версия старого csc? Roslyn? • Версия CLR: CLR2? CLR4? CoreCLR? Mono? • Версия ОС: Windows? Linux? MacOS? FreeBSD? • Версия JIT: x86? x64? RyuJIT? • Версия GC: MS (какой CLR?)? Mono (Boehm/Sgen)? • Компиляция: JIT? NGen? .NET Native? • Железо: ??? • ... 5/48 Теория
  • 6. Запуск бенчмарка • Release build • Без дебаггера • Выключите другие приложения • Используйте максимальную производительность 6/48 Теория
  • 7. DateTime vs Stopwatch var start = DateTime.Now; Foo(); var finish = DateTime.Now; Console.WriteLine((finish - start).Milliseconds); vs var sw = Stopwatch.StartNew(); Foo(); sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds); 7/48 Теория
  • 8. DateTime vs Stopwatch var start = DateTime.Now; Foo(); var finish = DateTime.Now; Console.WriteLine((finish - start).Milliseconds); vs var sw = Stopwatch.StartNew(); Foo(); sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds); Возможные значения (Windows 10, MS.NET, Core i7): Granularity Latency DateTime 1 000 000 ns∗ 30–40 ns Stopwatch 370–466 ns 14–18 ns 7/48 Теория
  • 9. Итерации Плохо: // Granularity(Stopwatch) = 466 ns // Latency(Stopwatch) = 18 ns var sw = Stopwatch.StartNew(); Foo(); // 100 ns sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds); 8/48 Теория
  • 10. Итерации Плохо: // Granularity(Stopwatch) = 466 ns // Latency(Stopwatch) = 18 ns var sw = Stopwatch.StartNew(); Foo(); // 100 ns sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds); Лучше: var sw = Stopwatch.StartNew(); for (int i = 0; i < N; i++) // (N * 100 + eps) ns Foo(); sw.Stop(); var total = sw.ElapsedTicks / Stopwatch.Frequency; Console.WriteLine(total / N); 8/48 Теория
  • 11. Прогрев Запустим бенчмарк несколько раз: int[] x = new int[128 * 1024 * 1024]; for (int iter = 0; iter < 5; iter++) { var sw = Stopwatch.StartNew(); for (int i = 0; i < x.Length; i += 16) x[i]++; sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds); } 9/48 Теория
  • 12. Прогрев Запустим бенчмарк несколько раз: int[] x = new int[128 * 1024 * 1024]; for (int iter = 0; iter < 5; iter++) { var sw = Stopwatch.StartNew(); for (int i = 0; i < x.Length; i += 16) x[i]++; sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds); } Результат: 176 81 62 62 62 9/48 Теория
  • 13. Несколько запусков бенчмарка Run 01 : 529.8674 ns/op Run 02 : 532.7541 ns/op Run 03 : 558.7448 ns/op Run 04 : 555.6647 ns/op Run 05 : 539.6401 ns/op Run 06 : 539.3494 ns/op Run 07 : 564.3222 ns/op Run 08 : 551.9544 ns/op Run 09 : 550.1608 ns/op Run 10 : 533.0634 ns/op 10/48 Теория
  • 16. Накладные расходы var sw = Stopwatch.StartNew(); int x = 0; for (int i = 0; i < N; i++) // overhead x++; // target operation sw.Stop(); 13/48 Теория
  • 18. Изоляция бенчмарков Плохо: Measure1(); Measure2(); Вспомним про: • Interface method dispatch • Garbage collector 14/48 Теория
  • 19. Борьба с оптимизациями • Dead code elimination • Inlining • Constant folding • Instruction Level Parallelism • Branch prediction • ... 15/48 Теория
  • 20. Организация доступа к памяти Event Latency Scaled 1 CPU cycle 0.3 ns 1 s Level 1 cache access 0.9 ns 3 s Level 2 cache access 2.8 ns 9 s Level 3 cache access 12.9 ns 43 s Main memory access 120 ns 6 min Solid-state disk I/O 50-150 µs 2-6 days Rotational disk I/O 1-10 ms 1-12 months Internet: SF to NYC 40 ms 4 years Internet: SF to UK 81 ms 8 years Internet: SF to Australia 183 ms 19 years OS virtualization reboot 4 s 423 years SCSI command time-out 30 s 3000 years Hardware virtualization reboot 40 s 4000 years Physical system reboot 5 m 32 millenia c Systems Performance: Enterprise and the Cloud 16/48 Теория
  • 22. Многопоточные бенчмарки сложны False sharing: c http://colfaxresearch.com/optimization-techniques-for-the-intel-mic-architecture- part-3-of-3-false-sharing-and-padding/ 18/48 Теория
  • 23. False sharing в действии private static int[] x = new int[1024]; private void Inc(int p) { for (int i = 0; i < 10000001; i++) x[p]++; } private void Run(int step) { var sw = Stopwatch.StartNew(); Task.WaitAll( Task.Factory.StartNew(() => Inc(0 * step)), Task.Factory.StartNew(() => Inc(1 * step)), Task.Factory.StartNew(() => Inc(2 * step)), Task.Factory.StartNew(() => Inc(3 * step))); Console.WriteLine(sw.ElapsedMilliseconds); } 19/48 Теория
  • 24. False sharing в действии private static int[] x = new int[1024]; private void Inc(int p) { for (int i = 0; i < 10000001; i++) x[p]++; } private void Run(int step) { var sw = Stopwatch.StartNew(); Task.WaitAll( Task.Factory.StartNew(() => Inc(0 * step)), Task.Factory.StartNew(() => Inc(1 * step)), Task.Factory.StartNew(() => Inc(2 * step)), Task.Factory.StartNew(() => Inc(3 * step))); Console.WriteLine(sw.ElapsedMilliseconds); } Run(1) Run(256) ∼400 ∼150 19/48 Теория
  • 26. BenchmarkDotNet v0.7.8: • Создание отдельного проекта для каждого бенчмарка • Запуск под разными окружениями • Прогрев, многократный запуск, статистики • Анализ накладных расходов • И много чего ещё... 21/48 Теория
  • 27. BenchmarkDotNet v0.7.8: • Создание отдельного проекта для каждого бенчмарка • Запуск под разными окружениями • Прогрев, многократный запуск, статистики • Анализ накладных расходов • И много чего ещё... В следующих сериях: • Просмотр IL и ASM • Графики • Поддержка CoreCLR/.NET Native • Многопоточные бенчмарки 21/48 Теория
  • 29. Сумма элементов массива const int N = 1024; int[,] a = new int[N, N]; [Benchmark] public double SumIj() { var sum = 0; for (int i = 0; i < N; i++) for (int j = 0; j < N; j++) sum += a[i, j]; return sum; } [Benchmark] public double SumJi() { var sum = 0; for (int j = 0; j < N; j++) for (int i = 0; i < N; i++) sum += a[i, j]; return sum; } 23/48 Практика
  • 30. Сумма элементов массива const int N = 1024; int[,] a = new int[N, N]; [Benchmark] public double SumIj() { var sum = 0; for (int i = 0; i < N; i++) for (int j = 0; j < N; j++) sum += a[i, j]; return sum; } [Benchmark] public double SumJi() { var sum = 0; for (int j = 0; j < N; j++) for (int i = 0; i < N; i++) sum += a[i, j]; return sum; } SumIj SumJi LegacyJIT-x86 1 попугай 3.5 попугая 23/48 Практика
  • 33. Branch prediction const int N = 32767; int[] sorted, unsorted; // random numbers [0..255] private static int Sum(int[] data) { int sum = 0; for (int i = 0; i < N; i++) if (data[i] >= 128) sum += data[i]; return sum; } [Benchmark] public int Sorted() { return Sum(sorted); } [Benchmark] public int Unsorted() { return Sum(unsorted); } 26/48 Практика
  • 34. Branch prediction const int N = 32767; int[] sorted, unsorted; // random numbers [0..255] private static int Sum(int[] data) { int sum = 0; for (int i = 0; i < N; i++) if (data[i] >= 128) sum += data[i]; return sum; } [Benchmark] public int Sorted() { return Sum(sorted); } [Benchmark] public int Unsorted() { return Sum(unsorted); } Sorted Unsorted LegacyJIT-x86 1 попугай 7.4 попугая 26/48 Практика
  • 35. Интерфейсы private interface IFoo { double Inc(double x); } private class Foo1 : IFoo { public double Inc(double x) => x + 1; } private class Foo2 : IFoo { public double Inc(double x) => x + 1; } private double Run(IFoo foo) { double sum = 0; for (int i = 0; i < 1001; i++) sum += foo.Inc(0); return sum; } [Benchmark] public double Run11() { var bar1 = new Foo1(); var bar2 = new Foo1(); return Run(bar1) + Run(bar2); } [Benchmark] public double Run12() { var bar1 = new Foo1(); var bar2 = new Foo2(); return Run(bar1) + Run(bar2); } 27/48 Практика
  • 36. Интерфейсы private interface IFoo { double Inc(double x); } private class Foo1 : IFoo { public double Inc(double x) => x + 1; } private class Foo2 : IFoo { public double Inc(double x) => x + 1; } private double Run(IFoo foo) { double sum = 0; for (int i = 0; i < 1001; i++) sum += foo.Inc(0); return sum; } [Benchmark] public double Run11() { var bar1 = new Foo1(); var bar2 = new Foo1(); return Run(bar1) + Run(bar2); } [Benchmark] public double Run12() { var bar1 = new Foo1(); var bar2 = new Foo2(); return Run(bar1) + Run(bar2); } Run11 Run12 LegacyJIT-x64 1 попугай 1.25 попугая 27/48 Практика
  • 38. 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; } 29/48 Практика
  • 39. Inlining [Benchmark] int Calc() => WithoutStarg(0x11) + WithStarg(0x12); int WithoutStarg(int value) => value; int WithStarg(int value) { if (value < 0) value = -value; return value; } 30/48 Практика
  • 40. 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 попугай 30/48 Практика
  • 41. Как же так? LegacyJIT-x64 ; LegacyJIT-x64 mov ecx,23h ret 31/48 Практика
  • 42. Как же так? 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 31/48 Практика
  • 44. Поговорим про 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; 33/48 Практика
  • 45. Поговорим про 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 попугая 33/48 Практика
  • 46. Как же так? ; 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 34/48 Практика
  • 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; 35/48 Практика
  • 48. Поговорим про 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 попугай 35/48 Практика
  • 49. Как же так? ; 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 36/48 Практика
  • 50. Думаем про ASM double x = /* ... */ ; double a = x + 1; double b = x * 2; double c = Math.Sqrt(x); 37/48 Практика
  • 51. Думаем про 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 37/48 Практика
  • 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); 39/48 Практика
  • 54. Задачка 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 39/48 Практика
  • 55. Как же так? 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 40/48 Практика
  • 56. Как же так? RyuJIT-x64, Sqrt14 vmovsd xmm0,qword ptr [7FF94F9C4C80h] ret 41/48 Практика
  • 57. Как же так? Большое дерево выражения * 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 // ... 42/48 Практика
  • 58. Как же так? 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]} ... 43/48 Практика
  • 60. Задачка 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; } 45/48 Практика
  • 61. Задачка 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 45/48 Практика
  • 62. Как же так? ; 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 46/48 Практика
  • 63. Методическая литература Для успешных микрооптимизаций нужно очень много знать: 47/48