10. Применения бенчмарков
• Performance analysis
• Сравнение алгоритмов
• Оценка улучшений производительности
• Анализ регрессии
• . . .
7/52 1. Почему мы об этом говорим?
11. Применения бенчмарков
• Performance analysis
• Сравнение алгоритмов
• Оценка улучшений производительности
• Анализ регрессии
• . . .
• Научный интерес
7/52 1. Почему мы об этом говорим?
12. Применения бенчмарков
• Performance analysis
• Сравнение алгоритмов
• Оценка улучшений производительности
• Анализ регрессии
• . . .
• Научный интерес
• Маркетинг
7/52 1. Почему мы об этом говорим?
13. Применения бенчмарков
• Performance analysis
• Сравнение алгоритмов
• Оценка улучшений производительности
• Анализ регрессии
• . . .
• Научный интерес
• Маркетинг
• Весёлое времяпрепровождение
7/52 1. Почему мы об этом говорим?
14. Сегодня в программе
• Увлекательные микробенчмарки на C#.
8/52 1. Почему мы об этом говорим?
15. Сегодня в программе
• Увлекательные микробенчмарки на C#.
∗ Слушатели с хорошим воображением легко перенесут основные
выводы на все остальные языки и рантаймы.
8/52 1. Почему мы об этом говорим?
18. Микробенчмаркинг
Плохой бенчмарк
// 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);
10/52 2. Количество итераций
19. Прогрев
Запустим бенчмарк несколько раз:
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);
}
11/52 2. Количество итераций
20. Прогрев
Запустим бенчмарк несколько раз:
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
11/52 2. Количество итераций
21. Несколько запусков метода
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
12/52 2. Количество итераций
26. Сумма элементов массива
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;
}
17/52 3. Работаем с памятью
27. Сумма элементов массива
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.3ms ≈4.0ms
17/52 3. Работаем с памятью
29. Часть 4
Работаем с условными переходами
19/52 4. Работаем с условными переходами
30. 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);
}
20/52 4. Работаем с условными переходами
31. 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 ≈20µs ≈139µs
20/52 4. Работаем с условными переходами
33. Загадка
Вопрос. Какая программа работает намного медленнее
остальных?
const int N = 100001;
public double Sum()
{
double x = 1, y = 1;
for (int i = 0; i < N; i++)
x = x + y;
return x;
}
// A
Sum();
// B
Write(Stopwatch.Frequency);
Sum();
22/52 5. Observer effect
34. Загадка
Вопрос. Какая программа работает намного медленнее
остальных?
const int N = 100001;
public double Sum()
{
double x = 1, y = 1;
for (int i = 0; i < N; i++)
x = x + y;
return x;
}
// A
Sum();
// B
Write(Stopwatch.Frequency);
Sum();
const int N = 100001;
public double Sum2()
{
double x = 1, y = 1;
var sw = Stopwatch.StartNew();
for (int i = 0; i < N; i++)
x = x + y;
sw.Stop();
return x;
}
// C
Sum2();
// D
Write(Stopwatch.Frequency);
Sum2();
22/52 5. Observer effect
35. Загадка
Вопрос. Какая программа работает намного медленнее
остальных?
const int N = 100001;
public double Sum()
{
double x = 1, y = 1;
for (int i = 0; i < N; i++)
x = x + y;
return x;
}
// A
Sum();
// B
Write(Stopwatch.Frequency);
Sum();
const int N = 100001;
public double Sum2()
{
double x = 1, y = 1;
var sw = Stopwatch.StartNew();
for (int i = 0; i < N; i++)
x = x + y;
sw.Stop();
return x;
}
// C
Sum2();
// D
Write(Stopwatch.Frequency);
Sum2();
Ответ. Если используется LegacyJIT-x86, то программа C:
A B C D
LegacyJIT-x86 ≈100µs ≈100µs ≈335µs ≈100µs
22/52 5. Observer effect
36. Отгадка
// D
// Статический конструктор класса
// Stopwatch вызывается при
// обращении к Stopwatch.Frequency
Write(Stopwatch.Frequency);
// Поэтому нам не нужно
// вызывать его из метода.
Sum2();
23/52 5. Observer effect
37. Отгадка
// D
// Статический конструктор класса
// Stopwatch вызывается при
// обращении к Stopwatch.Frequency
Write(Stopwatch.Frequency);
// Поэтому нам не нужно
// вызывать его из метода.
Sum2();
// C
// Статический конструктор класса
// Stopwatch ещё не вызывался.
// Поэтому нам придётся
// вызвать его из метода.
Sum2();
23/52 5. Observer effect
38. Отгадка
// D
// Статический конструктор класса
// Stopwatch вызывается при
// обращении к Stopwatch.Frequency
Write(Stopwatch.Frequency);
// Поэтому нам не нужно
// вызывать его из метода.
Sum2();
// C
// Статический конструктор класса
// Stopwatch ещё не вызывался.
// Поэтому нам придётся
// вызвать его из метода.
Sum2();
; x + y (A, B, D)
fld1
faddp st(1),st
; x + y (C)
fld1
fadd qword ptr [ebp-0Ch]
fstp qword ptr [ebp-0Ch]
23/52 5. Observer effect
47. Ещё один весёлый пример
private int[,] a;
[Params(511, 512, 513)] public int N;
[Setup] public void Setup() => a = new int[N, N];
// Ищем максимальный элемент в колонке
[Benchmark] public int Max() {
int max = 0;
for (int i = 0; i < N; i++)
max = Math.Max(max, a[i, 0]);
return max;
}
∗
Windows 10, .NET 4.6.2, LegacyJIT-x86, Skylake
31/52 7. CPU Cache Associativity
48. Результаты зависят от N
N Max
511 ≈844ns
512 ≈1330ns
513 ≈844ns
32/52 7. CPU Cache Associativity
52. Минутка занимательного про Skylake
Почитаем Intel® 64 and IA-32 Architectures Optimization Reference Manual:
2.1.3. THE SKYLAKE MICROARCHITECTURE: Cache and Memory Subsystem
• Simultaneous handling of more loads and stores enabled by enlarged
buffers.
• Page split load penalty down from 100 cycles in previous generation
to 5 cycles.
• L3 write bandwidth increased from 4 cycles per line in previous
generation to 2 per line.
• L2 associativity changed from 8 ways to 4 ways.
35/52 7. CPU Cache Associativity
54. Очень простые структурки
public struct Struct3 {
public byte X1, X2, X3;
}
public struct Struct8 {
public byte X1, X2, X3, X4, X5, X6, X7, X8;
}
private Struct3[] struct3 = new Struct3[256];
private Struct8[] struct8 = new Struct8[256];
37/52 8. Выравнивание
55. Очень простой бенчмарк
[Benchmark] public int S3() {
int res = 0;
for (int i = 0; i < struct3.Length; i++) {
var s = struct3[i]; res += s.X1 + s.X2;
}
return res;
}
[Benchmark] public int S8() {
int res = 0;
for (int i = 0; i < struct8.Length; i++) {
var s = struct8[i]; res += s.X1 + s.X2;
}
return res;
}
38/52 8. Выравнивание
58. Тот же самый бенчмарк
// А что будет под RyuJIT-x64?
[Benchmark] public int S3() {
int res = 0;
for (int i = 0; i < struct3.Length; i++) {
var s = struct3[i]; res += s.X1 + s.X2;
}
return res;
}
[Benchmark] public int S8() {
int res = 0;
for (int i = 0; i < struct8.Length; i++) {
var s = struct8[i]; res += s.X1 + s.X2;
}
return res;
}
40/52 8. Выравнивание
61. (Текущая реализация) Struct promotion
Иногда RyuJIT1 может аллоцировать структуру
в регистрах, а не на стеке
• The struct must not be address-taken.
• It must have no overlapping fields.
• It must have only primitive fields.
• It must not be an argument or a return value that is passed in
registers.
• It can’t be larger than 32 bytes.
• It can’t have more than 4 fields.
1
Справедливо для v4.6.1637.0, поведение может поменяться, см. coreclr#6733
43/52 8. Выравнивание
64. Хорошие инструменты
• BenchmarkDotNet для .NET
• JMH для Java
• google/benchmark для C++
• rbenchmark для R
• ...
∗ Если вы используете хороший инструмент, то это не означает,
что ваш бенчмарк тоже хороший.
45/52 9. Заключение