10. Применения бенчмарков
• Performance analysis
• Сравнение алгоритмов
• Оценка улучшений производительности
7/85 1.1 Теория: Почему мы об этом говорим?
11. Применения бенчмарков
• Performance analysis
• Сравнение алгоритмов
• Оценка улучшений производительности
• Анализ регрессии
7/85 1.1 Теория: Почему мы об этом говорим?
12. Применения бенчмарков
• Performance analysis
• Сравнение алгоритмов
• Оценка улучшений производительности
• Анализ регрессии
• . . .
7/85 1.1 Теория: Почему мы об этом говорим?
13. Применения бенчмарков
• Performance analysis
• Сравнение алгоритмов
• Оценка улучшений производительности
• Анализ регрессии
• . . .
• Научный интерес
7/85 1.1 Теория: Почему мы об этом говорим?
14. Применения бенчмарков
• Performance analysis
• Сравнение алгоритмов
• Оценка улучшений производительности
• Анализ регрессии
• . . .
• Научный интерес
• Маркетинг
7/85 1.1 Теория: Почему мы об этом говорим?
15. Применения бенчмарков
• Performance analysis
• Сравнение алгоритмов
• Оценка улучшений производительности
• Анализ регрессии
• . . .
• Научный интерес
• Маркетинг
• Весёлое времяпрепровождение
7/85 1.1 Теория: Почему мы об этом говорим?
21. План performance-работ
1 Поставить задачу
2 Выбрать метрики
3 Выбрать инструмент
4 Провести эксперимент
9/85 1.2 Теория: Общая методология
22. План performance-работ
1 Поставить задачу
2 Выбрать метрики
3 Выбрать инструмент
4 Провести эксперимент
5 Получить результаты
9/85 1.2 Теория: Общая методология
23. План performance-работ
1 Поставить задачу
2 Выбрать метрики
3 Выбрать инструмент
4 Провести эксперимент
5 Получить результаты
6 Выполнить анализ и сделать выводы
9/85 1.2 Теория: Общая методология
24. План performance-работ
1 Поставить задачу
2 Выбрать метрики
3 Выбрать инструмент
4 Провести эксперимент
5 Получить результаты
6 Выполнить анализ и сделать выводы
Анализ полученных данных — самый важный этап
9/85 1.2 Теория: Общая методология
37. Performance spaces
Непонимание проблематики влечёт за собой
следующие проблемы:
• Легко обмануть себя, сделать неправильные
выводы, принять вредные бизнес-решения
12/85 1.2 Теория: Общая методология
38. Performance spaces
Непонимание проблематики влечёт за собой
следующие проблемы:
• Легко обмануть себя, сделать неправильные
выводы, принять вредные бизнес-решения
• Легко пропустить важную конфигурацию,
которая испортит жизнь в продакшене
12/85 1.2 Теория: Общая методология
39. Performance spaces
Непонимание проблематики влечёт за собой
следующие проблемы:
• Легко обмануть себя, сделать неправильные
выводы, принять вредные бизнес-решения
• Легко пропустить важную конфигурацию,
которая испортит жизнь в продакшене
• Легко повестись на кривые бенчмарки или
чёрный маркетинг
12/85 1.2 Теория: Общая методология
40. Окружение
C# compiler старый csc / Roslyn
CLR CLR2 / CLR4 / CoreCLR / Mono
OS Windows / Linux / MacOS / FreeBSD
JIT LegacyJIT-x86 / LegacyJIT-x64 / RyuJIT-x64
GC MS (разные CLR) / Mono (Boehm/Sgen)
Toolchain JIT / NGen / .NET Native
Hardware тысячи его
. . . . . .
13/85 1.2 Теория: Общая методология
41. Окружение
C# compiler старый csc / Roslyn
CLR CLR2 / CLR4 / CoreCLR / Mono
OS Windows / Linux / MacOS / FreeBSD
JIT LegacyJIT-x86 / LegacyJIT-x64 / RyuJIT-x64
GC MS (разные CLR) / Mono (Boehm/Sgen)
Toolchain JIT / NGen / .NET Native
Hardware тысячи его
. . . . . .
И не забываем про версии, много-много версий
13/85 1.2 Теория: Общая методология
57. DateTime vs Stopwatch
var start = DateTime.Now;
Foo();
var finish = DateTime.Now;
var time = (finish - start).TotalMilliseconds;
vs
var sw = Stopwatch.StartNew();
Foo();
sw.Stop();
var time = sw.ElapsedMilliseconds;
18/85 1.3 Теория: Таймеры
61. Характеристики таймеров
• Монотонность
замеры должны неуменьшаться
• Resolution
минимальное положительное время между
замерами
• Latency
время на получение замера
19/85 1.3 Теория: Таймеры
66. DateTime.UtcNow
OS Implementation
Windows GetSystemTimeAsFileTime
Linux gettimeofday
OS Runtime Time∗
Latency Windows Full/Core ≈7–8ns
Latency Windows Mono ≈30–31ns
Resolution Windows Any ≈0.5..15.625ms
Latency Linux Mono ≈26–30ns
Resolution Linux Mono ≈1µs
∗
Intel i7-4702MQ CPU 2.20GHz
См. также: http://aakinshin.net/en/blog/dotnet/datetime/
20/85 1.3 Теория: Таймеры
72. Важно помнить про таймеры
• Важно понимать значения Latency и Resolution
23/85 1.3 Теория: Таймеры
73. Важно помнить про таймеры
• Важно понимать значения Latency и Resolution
• 1 tick ≠ Resolution
23/85 1.3 Теория: Таймеры
74. Важно помнить про таймеры
• Важно понимать значения Latency и Resolution
• 1 tick ≠ Resolution
• Время может идти назад
23/85 1.3 Теория: Таймеры
75. Важно помнить про таймеры
• Важно понимать значения Latency и Resolution
• 1 tick ≠ Resolution
• Время может идти назад
• Два последовательных замера могут быть равны
23/85 1.3 Теория: Таймеры
76. Важно помнить про таймеры
• Важно понимать значения Latency и Resolution
• 1 tick ≠ Resolution
• Время может идти назад
• Два последовательных замера могут быть равны
• Два последовательных замера могут различаться на
миллисекунды
23/85 1.3 Теория: Таймеры
79. Микробенчмаркинг
Плохой бенчмарк
// Resolution(Stopwatch) = 466 ns
// Latency(Stopwatch) = 18 ns
var sw = Stopwatch.StartNew();
Foo(); // 100 ns
sw.Stop();
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;
WriteLine(total / N);
25/85 1.4 Теория: Количество итераций
80. Прогрев
Запустим бенчмарк несколько раз:
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);
}
26/85 1.4 Теория: Количество итераций
81. Прогрев
Запустим бенчмарк несколько раз:
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
26/85 1.4 Теория: Количество итераций
82. Несколько запусков метода
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
27/85 1.4 Теория: Количество итераций
87. Накладные расходы
var sw = Stopwatch.StartNew();
int x = 0;
for (int i = 0; i < N; i++) // overhead
x++; // target operation
sw.Stop();
32/85 1.5 Теория: Различные сложности
88. Изоляция бенчмарков
Плохой бенчмарк
var sw1 = Stopwatch.StartNew();
Foo();
sw1.Stop();
var sw2 = Stopwatch.StartNew();
Bar();
sw2.Stop();
33/85 1.5 Теория: Различные сложности
89. Изоляция бенчмарков
Плохой бенчмарк
var sw1 = Stopwatch.StartNew();
Foo();
sw1.Stop();
var sw2 = Stopwatch.StartNew();
Bar();
sw2.Stop();
Вспомним про:
• Interface method dispatch
• Garbage collector and autotuning
• Jitting
33/85 1.5 Теория: Различные сложности
93. Борьба с оптимизациями
• Dead code elimination
• Inlining
• Constant folding
34/85 1.5 Теория: Различные сложности
94. Борьба с оптимизациями
• Dead code elimination
• Inlining
• Constant folding
• Instruction Level Parallelism
34/85 1.5 Теория: Различные сложности
95. Борьба с оптимизациями
• Dead code elimination
• Inlining
• Constant folding
• Instruction Level Parallelism
• Branch prediction
34/85 1.5 Теория: Различные сложности
96. Борьба с оптимизациями
• Dead code elimination
• Inlining
• Constant folding
• Instruction Level Parallelism
• Branch prediction
• . . .
34/85 1.5 Теория: Различные сложности
101. 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);
}
39/85 1.5 Теория: Различные сложности
102. 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)
≈400ms ≈150ms
39/85 1.5 Теория: Различные сложности
103. Бенчмаркинг — это сложно
Anon et al., “A Measure of Transaction Processing Power”
There are lies, damn lies and then there are performance
measures.
40/85 1.5 Теория: Различные сложности
106. Задача
Какой из методов работает быстрее?
[MethodImpl(MethodImplOptions.NoInlining)]
public void Empty0() {}
[MethodImpl(MethodImplOptions.NoInlining)]
public void Empty1() {}
[MethodImpl(MethodImplOptions.NoInlining)]
public void Empty2() {}
[MethodImpl(MethodImplOptions.NoInlining)]
public void Empty3() {}
43/85 2.1 Практика: Сложности нанобенчмаркинга
107. Попытка решения
Давайте забенчмаркаем!
private void MeasureX() // X = 0, 1, 2, 3
{
for (int i = 0; i < Rep; i++)
{
var sw = Stopwatch.StartNew();
for (int j = 0; j < N; j++)
EmptyX(); // X = 0, 1, 2, 3
sw.Stop();
Write(sw.ElapsedMilliseconds + " ");
}
}
44/85 2.1 Практика: Сложности нанобенчмаркинга
108. Попытка решения
Давайте забенчмаркаем!
private void MeasureX() // X = 0, 1, 2, 3
{
for (int i = 0; i < Rep; i++)
{
var sw = Stopwatch.StartNew();
for (int j = 0; j < N; j++)
EmptyX(); // X = 0, 1, 2, 3
sw.Stop();
Write(sw.ElapsedMilliseconds + " ");
}
}
Empty0: 242 253 245 253 242 244 245 255 245 245 // Slow
Empty1: 224 228 229 224 223 224 227 222 228 222 // Fast
Empty2: 229 222 226 222 224 226 227 229 225 230 // Fast
Empty3: 241 240 237 244 242 241 238 245 239 239 // Slow
Intel Core i7 Haswell, RyuJIT-x64
44/85 2.1 Практика: Сложности нанобенчмаркинга
109. Обратимся к классике
Рекомендуемая литература:
Agner Fog,
“The microarchitecture of Intel, AMD and VIA CPUs.
An optimization guide for assembly programmers and
compiler makers.”
45/85 2.1 Практика: Сложности нанобенчмаркинга
110. Обратимся к классике
Рекомендуемая литература:
Agner Fog,
“The microarchitecture of Intel, AMD and VIA CPUs.
An optimization guide for assembly programmers and
compiler makers.”
3.8 Branch prediction in Intel Haswell, Broadwell and Skylake
Pattern recognition for indirect jumps and calls.
Indirect jumps and indirect calls are predicted well.
45/85 2.1 Практика: Сложности нанобенчмаркинга
113. Раскрутим наш цикл
Есть ли статистически значимая разница между замерами?
48/85 2.1 Практика: Сложности нанобенчмаркинга
114. Работа над ошибками
• Выключайте приложения во время
бенчмаркинга!
Результаты без запущенной VisualStudio:
• Не пытайтесь оценивать статистическую
значимость на глаз.
49/85 2.1 Практика: Сложности нанобенчмаркинга
116. Сумма элементов массива
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;
}
51/85 2.2 Практика: Работаем с памятью
117. Сумма элементов массива
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 попугая
51/85 2.2 Практика: Работаем с памятью
118. Часть 2.3
Работаем с условными
переходами
52/85 2.3 Практика: Работаем с условными переходами
119. 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);
}
53/85 2.3 Практика: Работаем с условными переходами
120. 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 попугая
53/85 2.3 Практика: Работаем с условными переходами
131. Как же так?
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
60/85 2.5 Практика: Inlining
136. Проведём ещё один опыт
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;
65/85 2.6 Практика: Readonly fields
137. Проведём ещё один опыт
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 попугая
65/85 2.6 Практика: Readonly fields
138. Как же так?
; 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
66/85 2.6 Практика: Readonly fields
153. Задачка
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;
}
79/85 2.9 Практика: Instruction level parallelism
154. Задачка
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
79/85 2.9 Практика: Instruction level parallelism
158. Отказ от ответственности
• Все представленные выводы и бенчмарки
могут быть враньём
82/85 3. Заключение
159. Отказ от ответственности
• Все представленные выводы и бенчмарки
могут быть враньём
• Использование BenchmarkDotNet не делает
ваш бенчмарк правильным
82/85 3. Заключение
160. Отказ от ответственности
• Все представленные выводы и бенчмарки
могут быть враньём
• Использование BenchmarkDotNet не делает
ваш бенчмарк правильным
• Использование самописных бенчмарков не
делает выводы ложными
82/85 3. Заключение
162. Сегодня мы узнали
• Бенчмаркинг и прочие замеры
производительности — это сложно
83/85 3. Заключение
163. Сегодня мы узнали
• Бенчмаркинг и прочие замеры
производительности — это сложно
• Бенчмаркинг требует очень много сил,
знаний, времени и нервов
83/85 3. Заключение
164. Сегодня мы узнали
• Бенчмаркинг и прочие замеры
производительности — это сложно
• Бенчмаркинг требует очень много сил,
знаний, времени и нервов
• Бенчмарк без анализа — плохой бенчмарк
83/85 3. Заключение