SlideShare a Scribd company logo
1 of 83
Download to read offline
Продолжаем говорить про арифметику
Андрей Акиньшин, JetBrains
DotNext 2016 Moscow
1/39
Знать математику — полезно
2/39
О чём будем разговаривать?
Сегодня в выпуске:
• Весёлые истории про арифметические баги
• Хитрые битовые хаки
• Удивительные рантаймы и JIT-компиляторы
• Коварные баги в .NET
• Неожиданные ASM-листинги
• Загадочный IEEE 754 и денормализованные числа
3/39
О чём будем разговаривать?
Сегодня в выпуске:
• Весёлые истории про арифметические баги
• Хитрые битовые хаки
• Удивительные рантаймы и JIT-компиляторы
• Коварные баги в .NET
• Неожиданные ASM-листинги
• Загадочный IEEE 754 и денормализованные числа
• Проблемы с точностью:
кто виноват и что делать?
• Скорость вычислений:
от чего зависит и как улучшать?
3/39
Отказ от ответственности
Нужно понимать:
• Все бенчмарки вымышлены, любые совпадения случайны
• Приведённые примеры нужны только для иллюстрации идей
• Погрешности малы, для целей доклада ими можно пренебречь
• На вашем железе цифры могут быть другие, это нормально
Конфигурация для всех примеров:
• BenchmarkDotNet v0.10.1 (supported by the .NET Foundation)
• Haswell Core i7-4702MQ CPU 2.20GHz, Windows 10
• .NET Framework 4.6.2 + clrjit/compatjit-v4.6.1586.0
• Mono 4.6.2 (x86/x64)
• Gist с исходниками: https://git.io/v1lUV
4/39
Quake 3 and Fast Inverse Square Root
5/39
Quake 3 and Fast Inverse Square Root
float Q_rsqrt(float number) { // 1/
√
number
long i; float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F; y = number;
i = *(long*)&y; // evil floating point bit level hacking
i = 0x5f3759df - (i >> 1); // what the fuck?
y = *(float*)&i;
y = y * (threehalfs - (x2 * y * y)); // 1st iteration
return y;
}
5/39
The Patriot Missile Failure
• Война в Персидском заливе, 25 февраля 1991
• Ракета Patriot не смогла сбить иракский снаряд Scud из-за бага
• Скорость Scud: ≈ 3750 миль в час
• Время хранилось в 24-битном регистре с точностью до 0.1 сек
• Ошибка на 1 тик составляет ≈ 9.5 · 10−8
сек
• Суммарная ошибка за 100 часов: 0.3433 сек
• Итоговая погрешность > 0.5 км
• 28 человек убиты, 98 ранены, cм. также GAO/IMTEC-92-26
6/39
Vancouver Stock Exchange
7/39
Vancouver Stock Exchange
• Январь 1982: введён индекс = 1000
7/39
Vancouver Stock Exchange
• Январь 1982: введён индекс = 1000
• После каждой транзакции индекс обновлялся и
округлялся до 3 знаков после точки (≈2800 раз в день)
7/39
Vancouver Stock Exchange
• Январь 1982: введён индекс = 1000
• После каждой транзакции индекс обновлялся и
округлялся до 3 знаков после точки (≈2800 раз в день)
• Ноябрь 1983: ребята скорректировали ошибки
Изменение индекса: 524.811 → 1098.892
7/39
Поговорим про битовые хаки
8/39
Ищем минимум
Задача. Дано два числа a и b типа int.
Необходимо реализовать функцию Min, которая
вернёт наименьшее из этих двух чисел.
9/39
Ищем минимум
Задача. Дано два числа a и b типа int.
Необходимо реализовать функцию Min, которая
вернёт наименьшее из этих двух чисел.
Решение A.
int MinA(int x, int y) => Math.Min(x, y);
9/39
Ищем минимум
Задача. Дано два числа a и b типа int.
Необходимо реализовать функцию Min, которая
вернёт наименьшее из этих двух чисел.
Решение A.
int MinA(int x, int y) => Math.Min(x, y);
Решение B.
int MinB(int x, int y) => x < y ? x : y;
9/39
Ищем минимум
Задача. Дано два числа a и b типа int.
Необходимо реализовать функцию Min, которая
вернёт наименьшее из этих двух чисел.
Решение A.
int MinA(int x, int y) => Math.Min(x, y);
Решение B.
int MinB(int x, int y) => x < y ? x : y;
Решение C.
int MinC(int x, int y) =>
x & ( (x - y) >> 31) |
y & (~(x - y) >> 31);
9/39
(Объяснение) Ищем минимум
int MinC(int x, int y) =>
x & ( (x - y) >> 31) |
y & (~(x - y) >> 31);
10/39
(Бенчмарки) Ищем минимум
const int N = 100001; int[] a, b, c; // int[N] arrays
[Benchmark] void MinB() {
for (int i = 0; i < N; i++) {
int x = a[i], y = b[i];
c[i] = x < y ? x : y;
}
}
[Benchmark] void MinC() {
for (int i = 0; i < N; i++) {
int x = a[i], y = b[i];
c[i] = x & ((x - y) >> 31) | y & (~(x - y) >> 31);
}
}
11/39
(Бенчмарки) Ищем минимум
const int N = 100001; int[] a, b, c; // int[N] arrays
[Benchmark] void MinB() {
for (int i = 0; i < N; i++) {
int x = a[i], y = b[i];
c[i] = x < y ? x : y;
}
}
[Benchmark] void MinC() {
for (int i = 0; i < N; i++) {
int x = a[i], y = b[i];
c[i] = x & ((x - y) >> 31) | y & (~(x - y) >> 31);
}
}
Random Const
MinB MinC MinB MinC
LegacyJIT-x86 ≈782µs ≈292µs ≈160µs ≈224µs
LegacyJIT-x64 ≈525µs ≈157µs ≈71µs ≈124µs
RyuJIT-x64 ≈602µs ≈248µs ≈221µs ≈292µs
Mono4.6.2-x64 ≈255µs ≈349µs ≈212µs ≈290µs
11/39
(ASM) Ищем минимум
; RyuJIT-x641
cmp ecx,edx
jl LESS
mov eax,edx
ret
LESS:
mov eax,ecx
ret
1
Roslyn 1.3.1.60616, /o+
12/39
(ASM) Ищем минимум
; RyuJIT-x641
cmp ecx,edx
jl LESS
mov eax,edx
ret
LESS:
mov eax,ecx
ret
; Mono4.6.2-x64
sub $0x18,%rsp
mov %rsi,(%rsp)
mov %rdi,0x8(%rsp)
mov %rcx,%rdi
mov %rdx,%rsi
cmp %esi,%edi
mov %rsi,%rax
cmovl % rdi,%rax
mov (%rsp),%rsi
mov 0x8(%rsp),%rdi
add $0x18,%rsp
retq
cmovl (0F 4C): Conditional move (if less)
1
Roslyn 1.3.1.60616, /o+
12/39
Делим на константу
Задача. Дано число n типа int. Необходимо
реализовать функцию Div3, которая делит
заданное число на 3.
13/39
Делим на константу
Задача. Дано число n типа int. Необходимо
реализовать функцию Div3, которая делит
заданное число на 3.
Решение A.
int Div3A(int n) =>
n / 3;
13/39
Делим на константу
Задача. Дано число n типа int. Необходимо
реализовать функцию Div3, которая делит
заданное число на 3.
Решение A.
int Div3A(int n) =>
n / 3;
Решение B.
int Div3B(int n) =>
(int)((n * 0xAAAAAAAB) >> 33);
13/39
Делим на константу
Задача. Дано число n типа int. Необходимо
реализовать функцию Div3, которая делит
заданное число на 3.
Решение A.
int Div3A(int n) =>
n / 3;
Решение B.
int Div3B(int n) =>
(int)((n * 0xAAAAAAAB) >> 33);
0xAAAAAAAB16 =
233
3
+ 1
13/39
(Бенчмарки) Делим на константу
int N = 100001; int[] a, b; // random arrays
[Benchmark] void Div3A() {
for (int i = 0; i < N; i++)
b[i] = a[i] / 3;
}
[Benchmark] void Div3B() {
for (int i = 0; i < N; i++)
b[i] = (int)((a[i] * 0xAAAAAAAB) >> 33);
}
14/39
(Бенчмарки) Делим на константу
int N = 100001; int[] a, b; // random arrays
[Benchmark] void Div3A() {
for (int i = 0; i < N; i++)
b[i] = a[i] / 3;
}
[Benchmark] void Div3B() {
for (int i = 0; i < N; i++)
b[i] = (int)((a[i] * 0xAAAAAAAB) >> 33);
}
Div3A Div3B
LegacyJIT-x86 ≈376µs ≈334µs
LegacyJIT-x64 ≈122µs ≈67µs
RyuJIT-x64 ≈173µs ≈140µs
Mono4.6.2-x86 ≈366µs ≈1 139µs
Mono4.6.2-x64 ≈446µs ≈176µs
14/39
(Объяснение) Делим на константу
long Mul(int x, uint y) => x * y;
15/39
(Объяснение) Делим на константу
long Mul(int x, uint y) => x * y;
; Mono-x86
gram_Mul:
push %rbp
mov %esp,%ebp
push %rdi
sub $0x34,%esp
cmpl $0xffffffff,0x8(%rbp)
setg %al
movzbl %al,%eax
mov 0x8(%rbp),%rcx
mov %ecx,-0x10(%rbp)
mov %eax,-0xc(%rbp)
mov 0xc(%rbp),%eax
mov %eax,-0x18(%rbp)
movl $0x0,-0x14(%rbp)
mov -0x14(%rbp),%eax
mov %eax,0xc(%rsp)
mov -0x18(%rbp),%eax
mov %eax,0x8(%rsp)
mov -0xc(%rbp),%eax
mov %eax,0x4(%rsp)
mov -0x10(%rbp),%eax
mov %eax,(%rsp)
callq c2d080c gram_Mul+0xc2d080c
mov %edx,-0xc(%rbp)
mov %eax,-0x10(%rbp)
mov 0x102ebc54(%rip),%eax
mov -0x10(%rbp),%ecx
mov %ecx,-0x18(%rbp)
mov -0xc(%rbp),%ecx
mov %ecx,-0x14(%rbp)
test %eax,%eax
jne gram_Mul+0xa8
jmp gram_Mul+0x72
mov -0x20(%rbp),%eax
mov %eax,-0x18(%rbp)
mov -0x1c(%rbp),%eax
mov %eax,-0x14(%rbp)
mov -0x18(%rbp),%eax
mov %eax,-0x10(%rbp)
mov -0x14(%rbp),%eax
; И ещё куча строк...
15/39
Нецелые числа — это сложно
16/39
Обычная арифметика не работает
(a + b) + c = a + (b + c)
(a · b) · c = a · (b · c)
(a + b) · c = a · c + b · c
ax+y
= ax
· ay
. . .
17/39
Обычная арифметика не работает
Жить сложно:
0.1d + 0.2d = 0.3d
0.1d ≈ 0.100000000000000005551115123125783
+0.2d ≈ 0.200000000000000011102230246251565
0.300000000000000044408920985006262
0.3d ≈ 0.299999999999999988897769753748435
18/39
Обычная арифметика не работает
Жить сложно:
0.1d + 0.2d = 0.3d
0.1d ≈ 0.100000000000000005551115123125783
+0.2d ≈ 0.200000000000000011102230246251565
0.300000000000000044408920985006262
0.3d ≈ 0.299999999999999988897769753748435
Хорошая практика:
bool AreEqual(double a, double b,
double eps = 1e-9)
{
return Math.Abs(a - b) < eps;
}
18/39
Поговорим про конвертацию
Задача. Что выведет следующий код?
double d1 = 0.84551240822557006;
string str = d1.ToString("R");1
double d2 = double.Parse(str);
WriteLine(d1 == d2);
19/39
Поговорим про конвертацию
Задача. Что выведет следующий код?
double d1 = 0.84551240822557006;
string str = d1.ToString("R");1
double d2 = double.Parse(str);
WriteLine(d1 == d2);
MSDN: Standard Numeric Format Strings
1
The round-trip ("R") format specifier is used to ensure
that a numeric value that is converted to a string will be
parsed back into the same numeric value.
19/39
Поговорим про конвертацию
Задача. Что выведет следующий код?
double d1 = 0.84551240822557006;
string str = d1.ToString("R");1
double d2 = double.Parse(str);
WriteLine(d1 == d2);
MSDN: Standard Numeric Format Strings
1
The round-trip ("R") format specifier is used to ensure
that a numeric value that is converted to a string will be
parsed back into the same numeric value.
.NET-x64 .NET-x86 Mono
false (баг) true true
19/39
Загадка на производительность
Вопрос. Какая программа работает намного
медленнее остальных под LegacyJIT-x86?
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();
20/39
Загадка на производительность
Вопрос. Какая программа работает намного
медленнее остальных под LegacyJIT-x86?
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();
20/39
Загадка на производительность
Вопрос. Какая программа работает намного
медленнее остальных под LegacyJIT-x86?
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();
Ответ. Программа C:
A B C D
LegacyJIT-x86 ≈100µs ≈100µs ≈335µs ≈100µs
20/39
Смотрим ASM
// D
// Статический конструктор класса
// Stopwatch вызывается при
// обращении к Stopwatch.Frequency
Write(Stopwatch.Frequency);
// Поэтому нам не нужно
// вызывать его из метода.
Sum2();
21/39
Смотрим ASM
// D
// Статический конструктор класса
// Stopwatch вызывается при
// обращении к Stopwatch.Frequency
Write(Stopwatch.Frequency);
// Поэтому нам не нужно
// вызывать его из метода.
Sum2();
// C
// Статический конструктор класса
// Stopwatch ещё не вызывался.
// Поэтому нам придётся
// вызвать его из метода.
Sum2();
21/39
Смотрим ASM
// 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]
21/39
IEEE 754
22/39
Нормализованные числа
V = (−1)S
· 1.M · 2E−Ebias
, Emin ≤ E ≤ Emax
23/39
Нормализованные числа
V = (−1)S
· 1.M · 2E−Ebias
, Emin ≤ E ≤ Emax
Sign Exponent Mantissa Ebias
float 1 8 23 127
double 1 11 52 1023
80bit 1 15 64 16383
23/39
Нормализованные числа
V = (−1)S
· 1.M · 2E−Ebias
, Emin ≤ E ≤ Emax
Sign Exponent Mantissa Ebias
float 1 8 23 127
double 1 11 52 1023
80bit 1 15 64 16383
Digits Lower Upper
float ≈ 7.2 1.2 · 10−38
3.4 · 10+38
double ≈ 15.9 2.3 · 10−308
1.7 · 10+308
80bit ≈ 19.2 3.4 · 10−4932
1.1 · 10+4932
23/39
Пример
S = 0
E = 100111002 = 15610
M = 1.110111001101011001010012 = 15 625 001 · 2−23
V = (−1)0
· 15 625 001 · 2−23
· 2156−127
= 15 625 001 · 26
= 1 000 000 064
24/39
Пример
S = 0
E = 100111002 = 15610
M = 1.110111001101011001010012 = 15 625 001 · 2−23
V = (−1)0
· 15 625 001 · 2−23
· 2156−127
= 15 625 001 · 26
= 1 000 000 064
Задача. Чему равно значение выражения?
((float)1000000064).ToString("G10")
24/39
Пример
S = 0
E = 100111002 = 15610
M = 1.110111001101011001010012 = 15 625 001 · 2−23
V = (−1)0
· 15 625 001 · 2−23
· 2156−127
= 15 625 001 · 26
= 1 000 000 064
Задача. Чему равно значение выражения?
((float)1000000064).ToString("G10")
Решение.
1000000060 // 9 значащих цифр
24/39
Денормализованные числа
V = (−1)S
· 0.M · 2Edenorm
, Edenorm = Ebias − 1
25/39
Денормализованные числа
V = (−1)S
· 0.M · 2Edenorm
, Edenorm = Ebias − 1
Edenorm Lower Upper
float -126 1.4 · 10−45
1.2 · 10−38
double -1022 5.0 · 10−324
2.3 · 10−308
80bit -16382 1.9 · 10−4951
3.4 · 10−4932
25/39
Денормализованные числа
V = (−1)S
· 0.M · 2Edenorm
, Edenorm = Ebias − 1
Edenorm Lower Upper
float -126 1.4 · 10−45
1.2 · 10−38
double -1022 5.0 · 10−324
2.3 · 10−308
80bit -16382 1.9 · 10−4951
3.4 · 10−4932
• Хорошие новости: a = b ⇒ a − b = 0
25/39
Денормализованные числа
V = (−1)S
· 0.M · 2Edenorm
, Edenorm = Ebias − 1
Edenorm Lower Upper
float -126 1.4 · 10−45
1.2 · 10−38
double -1022 5.0 · 10−324
2.3 · 10−308
80bit -16382 1.9 · 10−4951
3.4 · 10−4932
• Хорошие новости: a = b ⇒ a − b = 0
• Плохие новости: денормализованные числа
работают медленно
25/39
(Задачка) Денормализованные числа
Задача. Подсчитать 0.96100 000
, 0.961 000 000
, используя double.
26/39
(Задачка) Денормализованные числа
Задача. Подсчитать 0.96100 000
, 0.961 000 000
, используя double.
Решение A.
public double PowerA() {
double res = 1.0;
for (int i = 0; i < N; i++)
res = res * 0.96;
return res;
}
26/39
(Задачка) Денормализованные числа
Задача. Подсчитать 0.96100 000
, 0.961 000 000
, используя double.
Решение A.
public double PowerA() {
double res = 1.0;
for (int i = 0; i < N; i++)
res = res * 0.96;
return res;
}
Решение B.
private double resB;
public double PowerB() {
resB = 1.0;
for (int i = 0; i < N; i++)
resB = resB * 0.96;
return resB;
}
26/39
(Задачка) Денормализованные числа
Задача. Подсчитать 0.96100 000
, 0.961 000 000
, используя double.
Решение A.
public double PowerA() {
double res = 1.0;
for (int i = 0; i < N; i++)
res = res * 0.96;
return res;
}
Решение B.
private double resB;
public double PowerB() {
resB = 1.0;
for (int i = 0; i < N; i++)
resB = resB * 0.96;
return resB;
}
Решение C.
public double PowerC() {
double res = 1.0;
for (int i = 0; i < N; i++)
res = res * 0.96
+ 0.1 - 0.1;
return res;
}
[Params(100000, 1000000)]
public int N;
26/39
(Бенчмарки) Денормализованные числа
N PowerA PowerB PowerC
LegacyJIT-x86 105
≈168µs ≈19 264µs ≈370µs
RyuJIT-x64 105
≈3 961µs ≈4 142µs ≈370µs
LegacyJIT-x86 106
≈152 770µs ≈227 781µs ≈3 851µs
RyuJIT-x64 106
≈47 956µs ≈49 670µs ≈3 782µs
27/39
(Бенчмарки) Денормализованные числа
N PowerA PowerB PowerC
LegacyJIT-x86 105
≈168µs ≈19 264µs ≈370µs
RyuJIT-x64 105
≈3 961µs ≈4 142µs ≈370µs
LegacyJIT-x86 106
≈152 770µs ≈227 781µs ≈3 851µs
RyuJIT-x64 106
≈47 956µs ≈49 670µs ≈3 782µs
Логичные вопросы:
• Почему PowerC работает так быстро?
• Почему PowerA работает очень быстро, но только на
LegacyJIT-x86 при N = 105
?
27/39
(Бенчмарки) Денормализованные числа
N PowerA PowerB PowerC
LegacyJIT-x86 105
≈168µs ≈19 264µs ≈370µs
RyuJIT-x64 105
≈3 961µs ≈4 142µs ≈370µs
LegacyJIT-x86 106
≈152 770µs ≈227 781µs ≈3 851µs
RyuJIT-x64 106
≈47 956µs ≈49 670µs ≈3 782µs
Логичные вопросы:
• Почему PowerC работает так быстро?
• Почему PowerA работает очень быстро, но только на
LegacyJIT-x86 при N = 105
?
Рассмотрим два случая:
• LegacyJIT-x64, Mono-x64 (SSE); RyuJIT-x64 (AVX)
• LegacyJIT-x86 (x87 FPU)
27/39
(SSE/AVX) Денормализованные числа
i PowerA PowerC
0 1.0 1.0
3FF0000000000000 3FF0000000000000
28/39
(SSE/AVX) Денормализованные числа
i PowerA PowerC
0 1.0 1.0
3FF0000000000000 3FF0000000000000
1 0.96 0.96000000000000008
3FEEB851EB851EB8 3FEEB851EB851EB9
28/39
(SSE/AVX) Денормализованные числа
i PowerA PowerC
0 1.0 1.0
3FF0000000000000 3FF0000000000000
1 0.96 0.96000000000000008
3FEEB851EB851EB8 3FEEB851EB851EB9
885 2.0419318345555615E-16 1.8041124150158794E-16
3CAD6D6617566397 3CAA000000000000
886 1.9602545611733389E-16 1.6653345369377348E-16
3CAC4010166769D8 3CA8000000000000
887 1.8818443787264053E-16 1.6653345369377348E-16
3CAB1EC7C3967A17 3CA8000000000000
28/39
(SSE/AVX) Денормализованные числа
i PowerA PowerC
0 1.0 1.0
3FF0000000000000 3FF0000000000000
1 0.96 0.96000000000000008
3FEEB851EB851EB8 3FEEB851EB851EB9
885 2.0419318345555615E-16 1.8041124150158794E-16
3CAD6D6617566397 3CAA000000000000
886 1.9602545611733389E-16 1.6653345369377348E-16
3CAC4010166769D8 3CA8000000000000
887 1.8818443787264053E-16 1.6653345369377348E-16
3CAB1EC7C3967A17 3CA8000000000000
18171 6.42285339593621E-323 1.6653345369377348E-16
000000000000000D 3CA8000000000000
18172 5.92878775009496E-323 1.6653345369377348E-16
000000000000000C 3CA8000000000000
18173 5.92878775009496E-323 1.6653345369377348E-16
000000000000000C 3CA8000000000000
28/39
(x87 FPU) Денормализованные числа
; PowerA (105
: ≈ 167µs 106
: ≈ 151 947µs)
fld qword ptr ds:[14D2E28h] ; 0.96
fmulp st(1),st ; Значение в регистре
; PowerB (105
: ≈ 19 079µs 106
: ≈ 226 219µs)
fld qword ptr ds:[892E20h] ; 0.96
fmul qword ptr [ecx+4]
fstp qword ptr [ecx+4] ; Значение в памяти
29/39
(x87 FPU) Денормализованные числа
; PowerA (105
: ≈ 167µs 106
: ≈ 151 947µs)
fld qword ptr ds:[14D2E28h] ; 0.96
fmulp st(1),st ; Значение в регистре
; PowerB (105
: ≈ 19 079µs 106
: ≈ 226 219µs)
fld qword ptr ds:[892E20h] ; 0.96
fmul qword ptr [ecx+4]
fstp qword ptr [ecx+4] ; Значение в памяти
Intel® 64 and IA-32 Architectures Software Developer’s Manual
§8.2 X87 FPU Data Types
With the exception of the 80-bit double extended-
precision format, all of data types exist in memory only.
When they are loaded into x87 FPU data registers, they
are converted into double extended-precision format and
operated on in that format.
When a denormal number is used as a source operand,
the x87 FPU automatically normalizes the number when
it is converted to double extended-precision format.
29/39
Поговорим про суммирование
30/39
Сумма чисел
Задача. Дан List<double>. Необходимо
реализовать функцию Sum, которая вернёт сумму
элементов этого списка.
31/39
(Проблема) Сумма чисел
Имеется следующая проблема1
:
1016
+ 1 = 1016
1
Научное название: absorption
32/39
(Проблема) Сумма чисел
Имеется следующая проблема1
:
1016
+ 1 = 1016
Вспоминаем мат. часть:
Digits Lower Upper
double ≈ 15.9 2.3 · 10−308
1.7 · 10+308
1
Научное название: absorption
32/39
(Алгоритм Кэхэна) Сумма чисел
double KahanSum(this List<double> list)
{
double sum = 0, error = 0;
foreach (var x in list)
{
var y = x - error;
var newSum = sum + y;
error = (newSum - sum) - y;
sum = newSum;
}
return sum;
}
33/39
(Пример) Сумма чисел
var a = new List<double>()
{ 1016
, 1, 1, . . . , 1
100 раз
};
WriteLine("{0:N}", a.Sum());
WriteLine("{0:N}", a.KahanSum());
// 10,000,000,000,000,000.00
// 10,000,000,000,000,100.00
34/39
(Пример) Сумма чисел
var a = new List<double>()
{ 1016
, 1, 1, . . . , 1
100 раз
};
WriteLine("{0:N}", a.Sum());
WriteLine("{0:N}", a.KahanSum());
// 10,000,000,000,000,000.00
// 10,000,000,000,000,100.00
Проблема возникает в самых разных местах:
• Банковская сфера
• Страховая сфера
• Численные алгоритмы
• Ситуации с операциями над числами разных порядков
34/39
(А теперь про скорость) Сумма чисел
Задача. Дан double[] длины 4000. Необходимо реализовать
функцию Sum, которая быстро вернёт сумму элементов массива.
35/39
(А теперь про скорость) Сумма чисел
Задача. Дан double[] длины 4000. Необходимо реализовать
функцию Sum, которая быстро вернёт сумму элементов массива.
Решение A.
public double Bcl() => a.Sum();
35/39
(А теперь про скорость) Сумма чисел
Задача. Дан double[] длины 4000. Необходимо реализовать
функцию Sum, которая быстро вернёт сумму элементов массива.
Решение A.
public double Bcl() => a.Sum();
Решение B.
public double Loop() {
double sum = 0;
for (int i = 0; i < a.Length; i++)
sum += a[i];
return sum;
}
35/39
(А теперь про скорость) Сумма чисел
Задача. Дан double[] длины 4000. Необходимо реализовать
функцию Sum, которая быстро вернёт сумму элементов массива.
Решение A.
public double Bcl() => a.Sum();
Решение B.
public double Loop() {
double sum = 0;
for (int i = 0; i < a.Length; i++)
sum += a[i];
return sum;
}
Решение C.
public double Unroll() {
double sum = 0;
for (int i = 0; i < a.Length; i += 4)
sum += a[i] + a[i + 1] + a[i + 2] + a[i + 3];
return sum;
}
35/39
(Бенчмарки) Сумма чисел
const int N = 4000; double[] a; // random double[N] array
[Benchmark] double Bcl() => a.Sum();
[Benchmark] double Loop() {
double sum = 0;
for (int i = 0; i < a.Length; i++)
sum += a[i];
return sum;
}
[Benchmark] double Unroll() {
double sum = 0;
for (int i = 0; i < a.Length; i += 4)
sum += a[i] + a[i + 1] + a[i + 2] + a[i + 3];
return sum;
}
36/39
(Бенчмарки) Сумма чисел
const int N = 4000; double[] a; // random double[N] array
[Benchmark] double Bcl() => a.Sum();
[Benchmark] double Loop() {
double sum = 0;
for (int i = 0; i < a.Length; i++)
sum += a[i];
return sum;
}
[Benchmark] double Unroll() {
double sum = 0;
for (int i = 0; i < a.Length; i += 4)
sum += a[i] + a[i + 1] + a[i + 2] + a[i + 3];
return sum;
}
Bcl Loop Unroll
LegacyJIT-x86 ≈23.4µs ≈4.0µs ≈1.6µs
LegacyJIT-x64 ≈28.3µs ≈4.0µs ≈1.8µs
RyuJIT-x64 ≈31.1µs ≈4.2µs ≈2.4µs
Mono4.6.2-x86 ≈72.7µs ≈14.3µs ≈2.2µs
Mono4.6.2-x64 ≈54.7µs ≈12.3µs ≈3.6µs
36/39
(Примерная схема) Сумма чисел
Instruction Level Parallelism спешит на помощь!
37/39
Методическая литература
38/39
Вопросы?
Андрей Акиньшин, JetBrains
http://aakinshin.net
https://github.com/AndreyAkinshin
https://twitter.com/andrey_akinshin
andrey.akinshin@gmail.com
39/39

More Related Content

What's hot

Об особенностях использования значимых типов в .NET
Об особенностях использования значимых типов в .NETОб особенностях использования значимых типов в .NET
Об особенностях использования значимых типов в .NETAndrey Akinshin
 
Лекция 7. Стандарт OpenMP (подолжение)
Лекция 7. Стандарт OpenMP (подолжение)Лекция 7. Стандарт OpenMP (подолжение)
Лекция 7. Стандарт OpenMP (подолжение)Mikhail Kurnosov
 
Лекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building BlocksЛекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building BlocksMikhail Kurnosov
 
Лекция 11: Методы разработки алгоритмов
Лекция 11: Методы разработки алгоритмовЛекция 11: Методы разработки алгоритмов
Лекция 11: Методы разработки алгоритмовMikhail Kurnosov
 
Лекция 10. Графы. Остовные деревья минимальной стоимости
Лекция 10. Графы. Остовные деревья минимальной стоимостиЛекция 10. Графы. Остовные деревья минимальной стоимости
Лекция 10. Графы. Остовные деревья минимальной стоимостиMikhail Kurnosov
 
Векторизация кода (семинар 3)
Векторизация кода (семинар 3)Векторизация кода (семинар 3)
Векторизация кода (семинар 3)Mikhail Kurnosov
 
Лекция 2: Абстрактные типы данных. Алгоритмы сортировки
Лекция 2: Абстрактные типы данных. Алгоритмы сортировкиЛекция 2: Абстрактные типы данных. Алгоритмы сортировки
Лекция 2: Абстрактные типы данных. Алгоритмы сортировкиMikhail Kurnosov
 
Векторизация кода (семинар 2)
Векторизация кода (семинар 2)Векторизация кода (семинар 2)
Векторизация кода (семинар 2)Mikhail Kurnosov
 
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMДмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMSergey Platonov
 
Лекция 2: Абстрактные типы данных. Алгоритмы сортировки
Лекция 2: Абстрактные типы данных. Алгоритмы сортировкиЛекция 2: Абстрактные типы данных. Алгоритмы сортировки
Лекция 2: Абстрактные типы данных. Алгоритмы сортировкиMikhail Kurnosov
 
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведенияДракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведенияPlatonov Sergey
 
Распространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложенийРаспространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложенийMikhail Shcherbakov
 
Семинар 3. Многопоточное программирование на OpenMP (часть 3)
Семинар 3. Многопоточное программирование на OpenMP (часть 3)Семинар 3. Многопоточное программирование на OpenMP (часть 3)
Семинар 3. Многопоточное программирование на OpenMP (часть 3)Mikhail Kurnosov
 
Семинар 5. Многопоточное программирование на OpenMP (часть 5)
Семинар 5. Многопоточное программирование на OpenMP (часть 5)Семинар 5. Многопоточное программирование на OpenMP (часть 5)
Семинар 5. Многопоточное программирование на OpenMP (часть 5)Mikhail Kurnosov
 
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Sergey Platonov
 
хитрости выведения типов
хитрости выведения типовхитрости выведения типов
хитрости выведения типовcorehard_by
 
Лекция 1: Введение в алгоритмы
Лекция 1: Введение в алгоритмыЛекция 1: Введение в алгоритмы
Лекция 1: Введение в алгоритмыMikhail Kurnosov
 
Метапрограммирование в C++11/14 и C++17. Новые инструменты - новые проблемы.
Метапрограммирование в C++11/14 и C++17. Новые инструменты - новые проблемы.Метапрограммирование в C++11/14 и C++17. Новые инструменты - новые проблемы.
Метапрограммирование в C++11/14 и C++17. Новые инструменты - новые проблемы.Roman Orlov
 
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building BlocksЛекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building BlocksMikhail Kurnosov
 
Лекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMPЛекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMPMikhail Kurnosov
 

What's hot (20)

Об особенностях использования значимых типов в .NET
Об особенностях использования значимых типов в .NETОб особенностях использования значимых типов в .NET
Об особенностях использования значимых типов в .NET
 
Лекция 7. Стандарт OpenMP (подолжение)
Лекция 7. Стандарт OpenMP (подолжение)Лекция 7. Стандарт OpenMP (подолжение)
Лекция 7. Стандарт OpenMP (подолжение)
 
Лекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building BlocksЛекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building Blocks
 
Лекция 11: Методы разработки алгоритмов
Лекция 11: Методы разработки алгоритмовЛекция 11: Методы разработки алгоритмов
Лекция 11: Методы разработки алгоритмов
 
Лекция 10. Графы. Остовные деревья минимальной стоимости
Лекция 10. Графы. Остовные деревья минимальной стоимостиЛекция 10. Графы. Остовные деревья минимальной стоимости
Лекция 10. Графы. Остовные деревья минимальной стоимости
 
Векторизация кода (семинар 3)
Векторизация кода (семинар 3)Векторизация кода (семинар 3)
Векторизация кода (семинар 3)
 
Лекция 2: Абстрактные типы данных. Алгоритмы сортировки
Лекция 2: Абстрактные типы данных. Алгоритмы сортировкиЛекция 2: Абстрактные типы данных. Алгоритмы сортировки
Лекция 2: Абстрактные типы данных. Алгоритмы сортировки
 
Векторизация кода (семинар 2)
Векторизация кода (семинар 2)Векторизация кода (семинар 2)
Векторизация кода (семинар 2)
 
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMДмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
 
Лекция 2: Абстрактные типы данных. Алгоритмы сортировки
Лекция 2: Абстрактные типы данных. Алгоритмы сортировкиЛекция 2: Абстрактные типы данных. Алгоритмы сортировки
Лекция 2: Абстрактные типы данных. Алгоритмы сортировки
 
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведенияДракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
 
Распространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложенийРаспространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложений
 
Семинар 3. Многопоточное программирование на OpenMP (часть 3)
Семинар 3. Многопоточное программирование на OpenMP (часть 3)Семинар 3. Многопоточное программирование на OpenMP (часть 3)
Семинар 3. Многопоточное программирование на OpenMP (часть 3)
 
Семинар 5. Многопоточное программирование на OpenMP (часть 5)
Семинар 5. Многопоточное программирование на OpenMP (часть 5)Семинар 5. Многопоточное программирование на OpenMP (часть 5)
Семинар 5. Многопоточное программирование на OpenMP (часть 5)
 
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
 
хитрости выведения типов
хитрости выведения типовхитрости выведения типов
хитрости выведения типов
 
Лекция 1: Введение в алгоритмы
Лекция 1: Введение в алгоритмыЛекция 1: Введение в алгоритмы
Лекция 1: Введение в алгоритмы
 
Метапрограммирование в C++11/14 и C++17. Новые инструменты - новые проблемы.
Метапрограммирование в C++11/14 и C++17. Новые инструменты - новые проблемы.Метапрограммирование в C++11/14 и C++17. Новые инструменты - новые проблемы.
Метапрограммирование в C++11/14 и C++17. Новые инструменты - новые проблемы.
 
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building BlocksЛекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
 
Лекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMPЛекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMP
 

Viewers also liked

Теория и практика .NET-бенчмаркинга (25.01.2017, Москва)
 Теория и практика .NET-бенчмаркинга (25.01.2017, Москва) Теория и практика .NET-бенчмаркинга (25.01.2017, Москва)
Теория и практика .NET-бенчмаркинга (25.01.2017, Москва)Andrey Akinshin
 
Теория и практика .NET-бенчмаркинга (02.11.2016, Екатеринбург)
Теория и практика .NET-бенчмаркинга (02.11.2016, Екатеринбург)Теория и практика .NET-бенчмаркинга (02.11.2016, Екатеринбург)
Теория и практика .NET-бенчмаркинга (02.11.2016, Екатеринбург)Andrey Akinshin
 
Let’s talk about microbenchmarking
Let’s talk about microbenchmarkingLet’s talk about microbenchmarking
Let’s talk about microbenchmarkingAndrey Akinshin
 
Сборка мусора в .NET
Сборка мусора в .NETСборка мусора в .NET
Сборка мусора в .NETAndrey Akinshin
 
Tree distance algorithm
Tree distance algorithmTree distance algorithm
Tree distance algorithmTrector Rancor
 
Нейронечёткая классификация слабо формализуемых данных | Тимур Гильмуллин
Нейронечёткая классификация слабо формализуемых данных | Тимур ГильмуллинНейронечёткая классификация слабо формализуемых данных | Тимур Гильмуллин
Нейронечёткая классификация слабо формализуемых данных | Тимур ГильмуллинPositive Hack Days
 

Viewers also liked (7)

Теория и практика .NET-бенчмаркинга (25.01.2017, Москва)
 Теория и практика .NET-бенчмаркинга (25.01.2017, Москва) Теория и практика .NET-бенчмаркинга (25.01.2017, Москва)
Теория и практика .NET-бенчмаркинга (25.01.2017, Москва)
 
Теория и практика .NET-бенчмаркинга (02.11.2016, Екатеринбург)
Теория и практика .NET-бенчмаркинга (02.11.2016, Екатеринбург)Теория и практика .NET-бенчмаркинга (02.11.2016, Екатеринбург)
Теория и практика .NET-бенчмаркинга (02.11.2016, Екатеринбург)
 
Let’s talk about microbenchmarking
Let’s talk about microbenchmarkingLet’s talk about microbenchmarking
Let’s talk about microbenchmarking
 
Сборка мусора в .NET
Сборка мусора в .NETСборка мусора в .NET
Сборка мусора в .NET
 
Phd presentation
Phd presentationPhd presentation
Phd presentation
 
Tree distance algorithm
Tree distance algorithmTree distance algorithm
Tree distance algorithm
 
Нейронечёткая классификация слабо формализуемых данных | Тимур Гильмуллин
Нейронечёткая классификация слабо формализуемых данных | Тимур ГильмуллинНейронечёткая классификация слабо формализуемых данных | Тимур Гильмуллин
Нейронечёткая классификация слабо формализуемых данных | Тимур Гильмуллин
 

Similar to Продолжаем говорить про арифметику

Tech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVMTech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVMTech Talks @NSU
 
Как приручить дракона: введение в LLVM
Как приручить дракона: введение в LLVMКак приручить дракона: введение в LLVM
Как приручить дракона: введение в LLVMTech Talks @NSU
 
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru GroupКак не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru GroupMail.ru Group
 
Komarov borba za-miesto-urfu_2013
Komarov borba za-miesto-urfu_2013Komarov borba za-miesto-urfu_2013
Komarov borba za-miesto-urfu_2013Yandex
 
Лекция 12. Быстрее, Python, ещё быстрее.
Лекция 12. Быстрее, Python, ещё быстрее.Лекция 12. Быстрее, Python, ещё быстрее.
Лекция 12. Быстрее, Python, ещё быстрее.Roman Brovko
 
Address Sanitizer или как сделать программы на c/с++ надежнее и безопаснее (К...
Address Sanitizer или как сделать программы на c/с++ надежнее и безопаснее (К...Address Sanitizer или как сделать программы на c/с++ надежнее и безопаснее (К...
Address Sanitizer или как сделать программы на c/с++ надежнее и безопаснее (К...Ontico
 
20130429 dynamic c_c++_program_analysis-alexey_samsonov
20130429 dynamic c_c++_program_analysis-alexey_samsonov20130429 dynamic c_c++_program_analysis-alexey_samsonov
20130429 dynamic c_c++_program_analysis-alexey_samsonovComputer Science Club
 
Лекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMPЛекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMPMikhail Kurnosov
 
DSLs in Lisp and Clojure
DSLs in Lisp and ClojureDSLs in Lisp and Clojure
DSLs in Lisp and ClojureVasil Remeniuk
 
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...Yandex
 
На что нужно обратить внимание при обзоре кода разрабатываемой библиотеки
На что нужно обратить внимание при обзоре кода разрабатываемой библиотекиНа что нужно обратить внимание при обзоре кода разрабатываемой библиотеки
На что нужно обратить внимание при обзоре кода разрабатываемой библиотекиAndrey Karpov
 
Как написать JIT компилятор
Как написать JIT компиляторКак написать JIT компилятор
Как написать JIT компиляторAndrew Aksyonoff
 
чернякова г.в.
чернякова г.в.чернякова г.в.
чернякова г.в.sharikdp
 
ОПК № 3 – Машинное представление целых чисел, символов, строк
ОПК № 3 – Машинное представление целых чисел, символов, строкОПК № 3 – Машинное представление целых чисел, символов, строк
ОПК № 3 – Машинное представление целых чисел, символов, строкVladimir Parfinenko
 
Советский суперкомпьютер К-340А и секретные вычисления
Советский суперкомпьютер К-340А и секретные вычисленияСоветский суперкомпьютер К-340А и секретные вычисления
Советский суперкомпьютер К-340А и секретные вычисленияPositive Hack Days
 
1 встреча — Параллельное программирование (А. Свириденков)
1 встреча — Параллельное программирование (А. Свириденков)1 встреча — Параллельное программирование (А. Свириденков)
1 встреча — Параллельное программирование (А. Свириденков)Smolensk Computer Science Club
 
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)Mikhail Kurnosov
 
Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)
Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)
Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)Mikhail Kurnosov
 

Similar to Продолжаем говорить про арифметику (20)

Tech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVMTech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVM
 
Как приручить дракона: введение в LLVM
Как приручить дракона: введение в LLVMКак приручить дракона: введение в LLVM
Как приручить дракона: введение в LLVM
 
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru GroupКак не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group
 
Komarov borba za-miesto-urfu_2013
Komarov borba za-miesto-urfu_2013Komarov borba za-miesto-urfu_2013
Komarov borba za-miesto-urfu_2013
 
Лекция 12. Быстрее, Python, ещё быстрее.
Лекция 12. Быстрее, Python, ещё быстрее.Лекция 12. Быстрее, Python, ещё быстрее.
Лекция 12. Быстрее, Python, ещё быстрее.
 
Address Sanitizer или как сделать программы на c/с++ надежнее и безопаснее (К...
Address Sanitizer или как сделать программы на c/с++ надежнее и безопаснее (К...Address Sanitizer или как сделать программы на c/с++ надежнее и безопаснее (К...
Address Sanitizer или как сделать программы на c/с++ надежнее и безопаснее (К...
 
20130429 dynamic c_c++_program_analysis-alexey_samsonov
20130429 dynamic c_c++_program_analysis-alexey_samsonov20130429 dynamic c_c++_program_analysis-alexey_samsonov
20130429 dynamic c_c++_program_analysis-alexey_samsonov
 
Лекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMPЛекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMP
 
DSLs in Lisp and Clojure
DSLs in Lisp and ClojureDSLs in Lisp and Clojure
DSLs in Lisp and Clojure
 
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
 
На что нужно обратить внимание при обзоре кода разрабатываемой библиотеки
На что нужно обратить внимание при обзоре кода разрабатываемой библиотекиНа что нужно обратить внимание при обзоре кода разрабатываемой библиотеки
На что нужно обратить внимание при обзоре кода разрабатываемой библиотеки
 
Как написать JIT компилятор
Как написать JIT компиляторКак написать JIT компилятор
Как написать JIT компилятор
 
OpenACC short review
OpenACC short reviewOpenACC short review
OpenACC short review
 
чернякова г.в.
чернякова г.в.чернякова г.в.
чернякова г.в.
 
ОПК № 3 – Машинное представление целых чисел, символов, строк
ОПК № 3 – Машинное представление целых чисел, символов, строкОПК № 3 – Машинное представление целых чисел, символов, строк
ОПК № 3 – Машинное представление целых чисел, символов, строк
 
Советский суперкомпьютер К-340А и секретные вычисления
Советский суперкомпьютер К-340А и секретные вычисленияСоветский суперкомпьютер К-340А и секретные вычисления
Советский суперкомпьютер К-340А и секретные вычисления
 
C++ exceptions
C++ exceptionsC++ exceptions
C++ exceptions
 
1 встреча — Параллельное программирование (А. Свириденков)
1 встреча — Параллельное программирование (А. Свириденков)1 встреча — Параллельное программирование (А. Свириденков)
1 встреча — Параллельное программирование (А. Свириденков)
 
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
 
Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)
Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)
Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)
 

More from Andrey Akinshin

Поговорим про performance-тестирование
Поговорим про performance-тестированиеПоговорим про performance-тестирование
Поговорим про performance-тестированиеAndrey Akinshin
 
Сложности performance-тестирования
Сложности performance-тестированияСложности performance-тестирования
Сложности performance-тестированияAndrey Akinshin
 
Поговорим про память
Поговорим про памятьПоговорим про память
Поговорим про памятьAndrey Akinshin
 
Кроссплатформенный .NET и как там дела с Mono и CoreCLR
Кроссплатформенный .NET и как там дела с Mono и CoreCLRКроссплатформенный .NET и как там дела с Mono и CoreCLR
Кроссплатформенный .NET и как там дела с Mono и CoreCLRAndrey Akinshin
 
Подружили CLR и JVM в Project Rider
Подружили CLR и JVM в Project RiderПодружили CLR и JVM в Project Rider
Подружили CLR и JVM в Project RiderAndrey Akinshin
 
Что нам готовит грядущий C#7?
Что нам готовит грядущий C#7?Что нам готовит грядущий C#7?
Что нам готовит грядущий C#7?Andrey Akinshin
 
.NET 2015: Будущее рядом
.NET 2015: Будущее рядом.NET 2015: Будущее рядом
.NET 2015: Будущее рядомAndrey Akinshin
 
Распространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложенийРаспространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложенийAndrey Akinshin
 
Практические приёмы оптимизации .NET-приложений
Практические приёмы оптимизации .NET-приложенийПрактические приёмы оптимизации .NET-приложений
Практические приёмы оптимизации .NET-приложенийAndrey Akinshin
 
Поговорим о различных версиях .NET
Поговорим о различных версиях .NETПоговорим о различных версиях .NET
Поговорим о различных версиях .NETAndrey Akinshin
 
Низкоуровневые оптимизации .NET-приложений
Низкоуровневые оптимизации .NET-приложенийНизкоуровневые оптимизации .NET-приложений
Низкоуровневые оптимизации .NET-приложенийAndrey Akinshin
 
Основы работы с Git
Основы работы с GitОсновы работы с Git
Основы работы с GitAndrey Akinshin
 

More from Andrey Akinshin (12)

Поговорим про performance-тестирование
Поговорим про performance-тестированиеПоговорим про performance-тестирование
Поговорим про performance-тестирование
 
Сложности performance-тестирования
Сложности performance-тестированияСложности performance-тестирования
Сложности performance-тестирования
 
Поговорим про память
Поговорим про памятьПоговорим про память
Поговорим про память
 
Кроссплатформенный .NET и как там дела с Mono и CoreCLR
Кроссплатформенный .NET и как там дела с Mono и CoreCLRКроссплатформенный .NET и как там дела с Mono и CoreCLR
Кроссплатформенный .NET и как там дела с Mono и CoreCLR
 
Подружили CLR и JVM в Project Rider
Подружили CLR и JVM в Project RiderПодружили CLR и JVM в Project Rider
Подружили CLR и JVM в Project Rider
 
Что нам готовит грядущий C#7?
Что нам готовит грядущий C#7?Что нам готовит грядущий C#7?
Что нам готовит грядущий C#7?
 
.NET 2015: Будущее рядом
.NET 2015: Будущее рядом.NET 2015: Будущее рядом
.NET 2015: Будущее рядом
 
Распространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложенийРаспространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложений
 
Практические приёмы оптимизации .NET-приложений
Практические приёмы оптимизации .NET-приложенийПрактические приёмы оптимизации .NET-приложений
Практические приёмы оптимизации .NET-приложений
 
Поговорим о различных версиях .NET
Поговорим о различных версиях .NETПоговорим о различных версиях .NET
Поговорим о различных версиях .NET
 
Низкоуровневые оптимизации .NET-приложений
Низкоуровневые оптимизации .NET-приложенийНизкоуровневые оптимизации .NET-приложений
Низкоуровневые оптимизации .NET-приложений
 
Основы работы с Git
Основы работы с GitОсновы работы с Git
Основы работы с Git
 

Продолжаем говорить про арифметику

  • 1. Продолжаем говорить про арифметику Андрей Акиньшин, JetBrains DotNext 2016 Moscow 1/39
  • 3. О чём будем разговаривать? Сегодня в выпуске: • Весёлые истории про арифметические баги • Хитрые битовые хаки • Удивительные рантаймы и JIT-компиляторы • Коварные баги в .NET • Неожиданные ASM-листинги • Загадочный IEEE 754 и денормализованные числа 3/39
  • 4. О чём будем разговаривать? Сегодня в выпуске: • Весёлые истории про арифметические баги • Хитрые битовые хаки • Удивительные рантаймы и JIT-компиляторы • Коварные баги в .NET • Неожиданные ASM-листинги • Загадочный IEEE 754 и денормализованные числа • Проблемы с точностью: кто виноват и что делать? • Скорость вычислений: от чего зависит и как улучшать? 3/39
  • 5. Отказ от ответственности Нужно понимать: • Все бенчмарки вымышлены, любые совпадения случайны • Приведённые примеры нужны только для иллюстрации идей • Погрешности малы, для целей доклада ими можно пренебречь • На вашем железе цифры могут быть другие, это нормально Конфигурация для всех примеров: • BenchmarkDotNet v0.10.1 (supported by the .NET Foundation) • Haswell Core i7-4702MQ CPU 2.20GHz, Windows 10 • .NET Framework 4.6.2 + clrjit/compatjit-v4.6.1586.0 • Mono 4.6.2 (x86/x64) • Gist с исходниками: https://git.io/v1lUV 4/39
  • 6. Quake 3 and Fast Inverse Square Root 5/39
  • 7. Quake 3 and Fast Inverse Square Root float Q_rsqrt(float number) { // 1/ √ number long i; float x2, y; const float threehalfs = 1.5F; x2 = number * 0.5F; y = number; i = *(long*)&y; // evil floating point bit level hacking i = 0x5f3759df - (i >> 1); // what the fuck? y = *(float*)&i; y = y * (threehalfs - (x2 * y * y)); // 1st iteration return y; } 5/39
  • 8. The Patriot Missile Failure • Война в Персидском заливе, 25 февраля 1991 • Ракета Patriot не смогла сбить иракский снаряд Scud из-за бага • Скорость Scud: ≈ 3750 миль в час • Время хранилось в 24-битном регистре с точностью до 0.1 сек • Ошибка на 1 тик составляет ≈ 9.5 · 10−8 сек • Суммарная ошибка за 100 часов: 0.3433 сек • Итоговая погрешность > 0.5 км • 28 человек убиты, 98 ранены, cм. также GAO/IMTEC-92-26 6/39
  • 10. Vancouver Stock Exchange • Январь 1982: введён индекс = 1000 7/39
  • 11. Vancouver Stock Exchange • Январь 1982: введён индекс = 1000 • После каждой транзакции индекс обновлялся и округлялся до 3 знаков после точки (≈2800 раз в день) 7/39
  • 12. Vancouver Stock Exchange • Январь 1982: введён индекс = 1000 • После каждой транзакции индекс обновлялся и округлялся до 3 знаков после точки (≈2800 раз в день) • Ноябрь 1983: ребята скорректировали ошибки Изменение индекса: 524.811 → 1098.892 7/39
  • 14. Ищем минимум Задача. Дано два числа a и b типа int. Необходимо реализовать функцию Min, которая вернёт наименьшее из этих двух чисел. 9/39
  • 15. Ищем минимум Задача. Дано два числа a и b типа int. Необходимо реализовать функцию Min, которая вернёт наименьшее из этих двух чисел. Решение A. int MinA(int x, int y) => Math.Min(x, y); 9/39
  • 16. Ищем минимум Задача. Дано два числа a и b типа int. Необходимо реализовать функцию Min, которая вернёт наименьшее из этих двух чисел. Решение A. int MinA(int x, int y) => Math.Min(x, y); Решение B. int MinB(int x, int y) => x < y ? x : y; 9/39
  • 17. Ищем минимум Задача. Дано два числа a и b типа int. Необходимо реализовать функцию Min, которая вернёт наименьшее из этих двух чисел. Решение A. int MinA(int x, int y) => Math.Min(x, y); Решение B. int MinB(int x, int y) => x < y ? x : y; Решение C. int MinC(int x, int y) => x & ( (x - y) >> 31) | y & (~(x - y) >> 31); 9/39
  • 18. (Объяснение) Ищем минимум int MinC(int x, int y) => x & ( (x - y) >> 31) | y & (~(x - y) >> 31); 10/39
  • 19. (Бенчмарки) Ищем минимум const int N = 100001; int[] a, b, c; // int[N] arrays [Benchmark] void MinB() { for (int i = 0; i < N; i++) { int x = a[i], y = b[i]; c[i] = x < y ? x : y; } } [Benchmark] void MinC() { for (int i = 0; i < N; i++) { int x = a[i], y = b[i]; c[i] = x & ((x - y) >> 31) | y & (~(x - y) >> 31); } } 11/39
  • 20. (Бенчмарки) Ищем минимум const int N = 100001; int[] a, b, c; // int[N] arrays [Benchmark] void MinB() { for (int i = 0; i < N; i++) { int x = a[i], y = b[i]; c[i] = x < y ? x : y; } } [Benchmark] void MinC() { for (int i = 0; i < N; i++) { int x = a[i], y = b[i]; c[i] = x & ((x - y) >> 31) | y & (~(x - y) >> 31); } } Random Const MinB MinC MinB MinC LegacyJIT-x86 ≈782µs ≈292µs ≈160µs ≈224µs LegacyJIT-x64 ≈525µs ≈157µs ≈71µs ≈124µs RyuJIT-x64 ≈602µs ≈248µs ≈221µs ≈292µs Mono4.6.2-x64 ≈255µs ≈349µs ≈212µs ≈290µs 11/39
  • 21. (ASM) Ищем минимум ; RyuJIT-x641 cmp ecx,edx jl LESS mov eax,edx ret LESS: mov eax,ecx ret 1 Roslyn 1.3.1.60616, /o+ 12/39
  • 22. (ASM) Ищем минимум ; RyuJIT-x641 cmp ecx,edx jl LESS mov eax,edx ret LESS: mov eax,ecx ret ; Mono4.6.2-x64 sub $0x18,%rsp mov %rsi,(%rsp) mov %rdi,0x8(%rsp) mov %rcx,%rdi mov %rdx,%rsi cmp %esi,%edi mov %rsi,%rax cmovl % rdi,%rax mov (%rsp),%rsi mov 0x8(%rsp),%rdi add $0x18,%rsp retq cmovl (0F 4C): Conditional move (if less) 1 Roslyn 1.3.1.60616, /o+ 12/39
  • 23. Делим на константу Задача. Дано число n типа int. Необходимо реализовать функцию Div3, которая делит заданное число на 3. 13/39
  • 24. Делим на константу Задача. Дано число n типа int. Необходимо реализовать функцию Div3, которая делит заданное число на 3. Решение A. int Div3A(int n) => n / 3; 13/39
  • 25. Делим на константу Задача. Дано число n типа int. Необходимо реализовать функцию Div3, которая делит заданное число на 3. Решение A. int Div3A(int n) => n / 3; Решение B. int Div3B(int n) => (int)((n * 0xAAAAAAAB) >> 33); 13/39
  • 26. Делим на константу Задача. Дано число n типа int. Необходимо реализовать функцию Div3, которая делит заданное число на 3. Решение A. int Div3A(int n) => n / 3; Решение B. int Div3B(int n) => (int)((n * 0xAAAAAAAB) >> 33); 0xAAAAAAAB16 = 233 3 + 1 13/39
  • 27. (Бенчмарки) Делим на константу int N = 100001; int[] a, b; // random arrays [Benchmark] void Div3A() { for (int i = 0; i < N; i++) b[i] = a[i] / 3; } [Benchmark] void Div3B() { for (int i = 0; i < N; i++) b[i] = (int)((a[i] * 0xAAAAAAAB) >> 33); } 14/39
  • 28. (Бенчмарки) Делим на константу int N = 100001; int[] a, b; // random arrays [Benchmark] void Div3A() { for (int i = 0; i < N; i++) b[i] = a[i] / 3; } [Benchmark] void Div3B() { for (int i = 0; i < N; i++) b[i] = (int)((a[i] * 0xAAAAAAAB) >> 33); } Div3A Div3B LegacyJIT-x86 ≈376µs ≈334µs LegacyJIT-x64 ≈122µs ≈67µs RyuJIT-x64 ≈173µs ≈140µs Mono4.6.2-x86 ≈366µs ≈1 139µs Mono4.6.2-x64 ≈446µs ≈176µs 14/39
  • 29. (Объяснение) Делим на константу long Mul(int x, uint y) => x * y; 15/39
  • 30. (Объяснение) Делим на константу long Mul(int x, uint y) => x * y; ; Mono-x86 gram_Mul: push %rbp mov %esp,%ebp push %rdi sub $0x34,%esp cmpl $0xffffffff,0x8(%rbp) setg %al movzbl %al,%eax mov 0x8(%rbp),%rcx mov %ecx,-0x10(%rbp) mov %eax,-0xc(%rbp) mov 0xc(%rbp),%eax mov %eax,-0x18(%rbp) movl $0x0,-0x14(%rbp) mov -0x14(%rbp),%eax mov %eax,0xc(%rsp) mov -0x18(%rbp),%eax mov %eax,0x8(%rsp) mov -0xc(%rbp),%eax mov %eax,0x4(%rsp) mov -0x10(%rbp),%eax mov %eax,(%rsp) callq c2d080c gram_Mul+0xc2d080c mov %edx,-0xc(%rbp) mov %eax,-0x10(%rbp) mov 0x102ebc54(%rip),%eax mov -0x10(%rbp),%ecx mov %ecx,-0x18(%rbp) mov -0xc(%rbp),%ecx mov %ecx,-0x14(%rbp) test %eax,%eax jne gram_Mul+0xa8 jmp gram_Mul+0x72 mov -0x20(%rbp),%eax mov %eax,-0x18(%rbp) mov -0x1c(%rbp),%eax mov %eax,-0x14(%rbp) mov -0x18(%rbp),%eax mov %eax,-0x10(%rbp) mov -0x14(%rbp),%eax ; И ещё куча строк... 15/39
  • 31. Нецелые числа — это сложно 16/39
  • 32. Обычная арифметика не работает (a + b) + c = a + (b + c) (a · b) · c = a · (b · c) (a + b) · c = a · c + b · c ax+y = ax · ay . . . 17/39
  • 33. Обычная арифметика не работает Жить сложно: 0.1d + 0.2d = 0.3d 0.1d ≈ 0.100000000000000005551115123125783 +0.2d ≈ 0.200000000000000011102230246251565 0.300000000000000044408920985006262 0.3d ≈ 0.299999999999999988897769753748435 18/39
  • 34. Обычная арифметика не работает Жить сложно: 0.1d + 0.2d = 0.3d 0.1d ≈ 0.100000000000000005551115123125783 +0.2d ≈ 0.200000000000000011102230246251565 0.300000000000000044408920985006262 0.3d ≈ 0.299999999999999988897769753748435 Хорошая практика: bool AreEqual(double a, double b, double eps = 1e-9) { return Math.Abs(a - b) < eps; } 18/39
  • 35. Поговорим про конвертацию Задача. Что выведет следующий код? double d1 = 0.84551240822557006; string str = d1.ToString("R");1 double d2 = double.Parse(str); WriteLine(d1 == d2); 19/39
  • 36. Поговорим про конвертацию Задача. Что выведет следующий код? double d1 = 0.84551240822557006; string str = d1.ToString("R");1 double d2 = double.Parse(str); WriteLine(d1 == d2); MSDN: Standard Numeric Format Strings 1 The round-trip ("R") format specifier is used to ensure that a numeric value that is converted to a string will be parsed back into the same numeric value. 19/39
  • 37. Поговорим про конвертацию Задача. Что выведет следующий код? double d1 = 0.84551240822557006; string str = d1.ToString("R");1 double d2 = double.Parse(str); WriteLine(d1 == d2); MSDN: Standard Numeric Format Strings 1 The round-trip ("R") format specifier is used to ensure that a numeric value that is converted to a string will be parsed back into the same numeric value. .NET-x64 .NET-x86 Mono false (баг) true true 19/39
  • 38. Загадка на производительность Вопрос. Какая программа работает намного медленнее остальных под LegacyJIT-x86? 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(); 20/39
  • 39. Загадка на производительность Вопрос. Какая программа работает намного медленнее остальных под LegacyJIT-x86? 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(); 20/39
  • 40. Загадка на производительность Вопрос. Какая программа работает намного медленнее остальных под LegacyJIT-x86? 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(); Ответ. Программа C: A B C D LegacyJIT-x86 ≈100µs ≈100µs ≈335µs ≈100µs 20/39
  • 41. Смотрим ASM // D // Статический конструктор класса // Stopwatch вызывается при // обращении к Stopwatch.Frequency Write(Stopwatch.Frequency); // Поэтому нам не нужно // вызывать его из метода. Sum2(); 21/39
  • 42. Смотрим ASM // D // Статический конструктор класса // Stopwatch вызывается при // обращении к Stopwatch.Frequency Write(Stopwatch.Frequency); // Поэтому нам не нужно // вызывать его из метода. Sum2(); // C // Статический конструктор класса // Stopwatch ещё не вызывался. // Поэтому нам придётся // вызвать его из метода. Sum2(); 21/39
  • 43. Смотрим ASM // 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] 21/39
  • 45. Нормализованные числа V = (−1)S · 1.M · 2E−Ebias , Emin ≤ E ≤ Emax 23/39
  • 46. Нормализованные числа V = (−1)S · 1.M · 2E−Ebias , Emin ≤ E ≤ Emax Sign Exponent Mantissa Ebias float 1 8 23 127 double 1 11 52 1023 80bit 1 15 64 16383 23/39
  • 47. Нормализованные числа V = (−1)S · 1.M · 2E−Ebias , Emin ≤ E ≤ Emax Sign Exponent Mantissa Ebias float 1 8 23 127 double 1 11 52 1023 80bit 1 15 64 16383 Digits Lower Upper float ≈ 7.2 1.2 · 10−38 3.4 · 10+38 double ≈ 15.9 2.3 · 10−308 1.7 · 10+308 80bit ≈ 19.2 3.4 · 10−4932 1.1 · 10+4932 23/39
  • 48. Пример S = 0 E = 100111002 = 15610 M = 1.110111001101011001010012 = 15 625 001 · 2−23 V = (−1)0 · 15 625 001 · 2−23 · 2156−127 = 15 625 001 · 26 = 1 000 000 064 24/39
  • 49. Пример S = 0 E = 100111002 = 15610 M = 1.110111001101011001010012 = 15 625 001 · 2−23 V = (−1)0 · 15 625 001 · 2−23 · 2156−127 = 15 625 001 · 26 = 1 000 000 064 Задача. Чему равно значение выражения? ((float)1000000064).ToString("G10") 24/39
  • 50. Пример S = 0 E = 100111002 = 15610 M = 1.110111001101011001010012 = 15 625 001 · 2−23 V = (−1)0 · 15 625 001 · 2−23 · 2156−127 = 15 625 001 · 26 = 1 000 000 064 Задача. Чему равно значение выражения? ((float)1000000064).ToString("G10") Решение. 1000000060 // 9 значащих цифр 24/39
  • 51. Денормализованные числа V = (−1)S · 0.M · 2Edenorm , Edenorm = Ebias − 1 25/39
  • 52. Денормализованные числа V = (−1)S · 0.M · 2Edenorm , Edenorm = Ebias − 1 Edenorm Lower Upper float -126 1.4 · 10−45 1.2 · 10−38 double -1022 5.0 · 10−324 2.3 · 10−308 80bit -16382 1.9 · 10−4951 3.4 · 10−4932 25/39
  • 53. Денормализованные числа V = (−1)S · 0.M · 2Edenorm , Edenorm = Ebias − 1 Edenorm Lower Upper float -126 1.4 · 10−45 1.2 · 10−38 double -1022 5.0 · 10−324 2.3 · 10−308 80bit -16382 1.9 · 10−4951 3.4 · 10−4932 • Хорошие новости: a = b ⇒ a − b = 0 25/39
  • 54. Денормализованные числа V = (−1)S · 0.M · 2Edenorm , Edenorm = Ebias − 1 Edenorm Lower Upper float -126 1.4 · 10−45 1.2 · 10−38 double -1022 5.0 · 10−324 2.3 · 10−308 80bit -16382 1.9 · 10−4951 3.4 · 10−4932 • Хорошие новости: a = b ⇒ a − b = 0 • Плохие новости: денормализованные числа работают медленно 25/39
  • 55. (Задачка) Денормализованные числа Задача. Подсчитать 0.96100 000 , 0.961 000 000 , используя double. 26/39
  • 56. (Задачка) Денормализованные числа Задача. Подсчитать 0.96100 000 , 0.961 000 000 , используя double. Решение A. public double PowerA() { double res = 1.0; for (int i = 0; i < N; i++) res = res * 0.96; return res; } 26/39
  • 57. (Задачка) Денормализованные числа Задача. Подсчитать 0.96100 000 , 0.961 000 000 , используя double. Решение A. public double PowerA() { double res = 1.0; for (int i = 0; i < N; i++) res = res * 0.96; return res; } Решение B. private double resB; public double PowerB() { resB = 1.0; for (int i = 0; i < N; i++) resB = resB * 0.96; return resB; } 26/39
  • 58. (Задачка) Денормализованные числа Задача. Подсчитать 0.96100 000 , 0.961 000 000 , используя double. Решение A. public double PowerA() { double res = 1.0; for (int i = 0; i < N; i++) res = res * 0.96; return res; } Решение B. private double resB; public double PowerB() { resB = 1.0; for (int i = 0; i < N; i++) resB = resB * 0.96; return resB; } Решение C. public double PowerC() { double res = 1.0; for (int i = 0; i < N; i++) res = res * 0.96 + 0.1 - 0.1; return res; } [Params(100000, 1000000)] public int N; 26/39
  • 59. (Бенчмарки) Денормализованные числа N PowerA PowerB PowerC LegacyJIT-x86 105 ≈168µs ≈19 264µs ≈370µs RyuJIT-x64 105 ≈3 961µs ≈4 142µs ≈370µs LegacyJIT-x86 106 ≈152 770µs ≈227 781µs ≈3 851µs RyuJIT-x64 106 ≈47 956µs ≈49 670µs ≈3 782µs 27/39
  • 60. (Бенчмарки) Денормализованные числа N PowerA PowerB PowerC LegacyJIT-x86 105 ≈168µs ≈19 264µs ≈370µs RyuJIT-x64 105 ≈3 961µs ≈4 142µs ≈370µs LegacyJIT-x86 106 ≈152 770µs ≈227 781µs ≈3 851µs RyuJIT-x64 106 ≈47 956µs ≈49 670µs ≈3 782µs Логичные вопросы: • Почему PowerC работает так быстро? • Почему PowerA работает очень быстро, но только на LegacyJIT-x86 при N = 105 ? 27/39
  • 61. (Бенчмарки) Денормализованные числа N PowerA PowerB PowerC LegacyJIT-x86 105 ≈168µs ≈19 264µs ≈370µs RyuJIT-x64 105 ≈3 961µs ≈4 142µs ≈370µs LegacyJIT-x86 106 ≈152 770µs ≈227 781µs ≈3 851µs RyuJIT-x64 106 ≈47 956µs ≈49 670µs ≈3 782µs Логичные вопросы: • Почему PowerC работает так быстро? • Почему PowerA работает очень быстро, но только на LegacyJIT-x86 при N = 105 ? Рассмотрим два случая: • LegacyJIT-x64, Mono-x64 (SSE); RyuJIT-x64 (AVX) • LegacyJIT-x86 (x87 FPU) 27/39
  • 62. (SSE/AVX) Денормализованные числа i PowerA PowerC 0 1.0 1.0 3FF0000000000000 3FF0000000000000 28/39
  • 63. (SSE/AVX) Денормализованные числа i PowerA PowerC 0 1.0 1.0 3FF0000000000000 3FF0000000000000 1 0.96 0.96000000000000008 3FEEB851EB851EB8 3FEEB851EB851EB9 28/39
  • 64. (SSE/AVX) Денормализованные числа i PowerA PowerC 0 1.0 1.0 3FF0000000000000 3FF0000000000000 1 0.96 0.96000000000000008 3FEEB851EB851EB8 3FEEB851EB851EB9 885 2.0419318345555615E-16 1.8041124150158794E-16 3CAD6D6617566397 3CAA000000000000 886 1.9602545611733389E-16 1.6653345369377348E-16 3CAC4010166769D8 3CA8000000000000 887 1.8818443787264053E-16 1.6653345369377348E-16 3CAB1EC7C3967A17 3CA8000000000000 28/39
  • 65. (SSE/AVX) Денормализованные числа i PowerA PowerC 0 1.0 1.0 3FF0000000000000 3FF0000000000000 1 0.96 0.96000000000000008 3FEEB851EB851EB8 3FEEB851EB851EB9 885 2.0419318345555615E-16 1.8041124150158794E-16 3CAD6D6617566397 3CAA000000000000 886 1.9602545611733389E-16 1.6653345369377348E-16 3CAC4010166769D8 3CA8000000000000 887 1.8818443787264053E-16 1.6653345369377348E-16 3CAB1EC7C3967A17 3CA8000000000000 18171 6.42285339593621E-323 1.6653345369377348E-16 000000000000000D 3CA8000000000000 18172 5.92878775009496E-323 1.6653345369377348E-16 000000000000000C 3CA8000000000000 18173 5.92878775009496E-323 1.6653345369377348E-16 000000000000000C 3CA8000000000000 28/39
  • 66. (x87 FPU) Денормализованные числа ; PowerA (105 : ≈ 167µs 106 : ≈ 151 947µs) fld qword ptr ds:[14D2E28h] ; 0.96 fmulp st(1),st ; Значение в регистре ; PowerB (105 : ≈ 19 079µs 106 : ≈ 226 219µs) fld qword ptr ds:[892E20h] ; 0.96 fmul qword ptr [ecx+4] fstp qword ptr [ecx+4] ; Значение в памяти 29/39
  • 67. (x87 FPU) Денормализованные числа ; PowerA (105 : ≈ 167µs 106 : ≈ 151 947µs) fld qword ptr ds:[14D2E28h] ; 0.96 fmulp st(1),st ; Значение в регистре ; PowerB (105 : ≈ 19 079µs 106 : ≈ 226 219µs) fld qword ptr ds:[892E20h] ; 0.96 fmul qword ptr [ecx+4] fstp qword ptr [ecx+4] ; Значение в памяти Intel® 64 and IA-32 Architectures Software Developer’s Manual §8.2 X87 FPU Data Types With the exception of the 80-bit double extended- precision format, all of data types exist in memory only. When they are loaded into x87 FPU data registers, they are converted into double extended-precision format and operated on in that format. When a denormal number is used as a source operand, the x87 FPU automatically normalizes the number when it is converted to double extended-precision format. 29/39
  • 69. Сумма чисел Задача. Дан List<double>. Необходимо реализовать функцию Sum, которая вернёт сумму элементов этого списка. 31/39
  • 70. (Проблема) Сумма чисел Имеется следующая проблема1 : 1016 + 1 = 1016 1 Научное название: absorption 32/39
  • 71. (Проблема) Сумма чисел Имеется следующая проблема1 : 1016 + 1 = 1016 Вспоминаем мат. часть: Digits Lower Upper double ≈ 15.9 2.3 · 10−308 1.7 · 10+308 1 Научное название: absorption 32/39
  • 72. (Алгоритм Кэхэна) Сумма чисел double KahanSum(this List<double> list) { double sum = 0, error = 0; foreach (var x in list) { var y = x - error; var newSum = sum + y; error = (newSum - sum) - y; sum = newSum; } return sum; } 33/39
  • 73. (Пример) Сумма чисел var a = new List<double>() { 1016 , 1, 1, . . . , 1 100 раз }; WriteLine("{0:N}", a.Sum()); WriteLine("{0:N}", a.KahanSum()); // 10,000,000,000,000,000.00 // 10,000,000,000,000,100.00 34/39
  • 74. (Пример) Сумма чисел var a = new List<double>() { 1016 , 1, 1, . . . , 1 100 раз }; WriteLine("{0:N}", a.Sum()); WriteLine("{0:N}", a.KahanSum()); // 10,000,000,000,000,000.00 // 10,000,000,000,000,100.00 Проблема возникает в самых разных местах: • Банковская сфера • Страховая сфера • Численные алгоритмы • Ситуации с операциями над числами разных порядков 34/39
  • 75. (А теперь про скорость) Сумма чисел Задача. Дан double[] длины 4000. Необходимо реализовать функцию Sum, которая быстро вернёт сумму элементов массива. 35/39
  • 76. (А теперь про скорость) Сумма чисел Задача. Дан double[] длины 4000. Необходимо реализовать функцию Sum, которая быстро вернёт сумму элементов массива. Решение A. public double Bcl() => a.Sum(); 35/39
  • 77. (А теперь про скорость) Сумма чисел Задача. Дан double[] длины 4000. Необходимо реализовать функцию Sum, которая быстро вернёт сумму элементов массива. Решение A. public double Bcl() => a.Sum(); Решение B. public double Loop() { double sum = 0; for (int i = 0; i < a.Length; i++) sum += a[i]; return sum; } 35/39
  • 78. (А теперь про скорость) Сумма чисел Задача. Дан double[] длины 4000. Необходимо реализовать функцию Sum, которая быстро вернёт сумму элементов массива. Решение A. public double Bcl() => a.Sum(); Решение B. public double Loop() { double sum = 0; for (int i = 0; i < a.Length; i++) sum += a[i]; return sum; } Решение C. public double Unroll() { double sum = 0; for (int i = 0; i < a.Length; i += 4) sum += a[i] + a[i + 1] + a[i + 2] + a[i + 3]; return sum; } 35/39
  • 79. (Бенчмарки) Сумма чисел const int N = 4000; double[] a; // random double[N] array [Benchmark] double Bcl() => a.Sum(); [Benchmark] double Loop() { double sum = 0; for (int i = 0; i < a.Length; i++) sum += a[i]; return sum; } [Benchmark] double Unroll() { double sum = 0; for (int i = 0; i < a.Length; i += 4) sum += a[i] + a[i + 1] + a[i + 2] + a[i + 3]; return sum; } 36/39
  • 80. (Бенчмарки) Сумма чисел const int N = 4000; double[] a; // random double[N] array [Benchmark] double Bcl() => a.Sum(); [Benchmark] double Loop() { double sum = 0; for (int i = 0; i < a.Length; i++) sum += a[i]; return sum; } [Benchmark] double Unroll() { double sum = 0; for (int i = 0; i < a.Length; i += 4) sum += a[i] + a[i + 1] + a[i + 2] + a[i + 3]; return sum; } Bcl Loop Unroll LegacyJIT-x86 ≈23.4µs ≈4.0µs ≈1.6µs LegacyJIT-x64 ≈28.3µs ≈4.0µs ≈1.8µs RyuJIT-x64 ≈31.1µs ≈4.2µs ≈2.4µs Mono4.6.2-x86 ≈72.7µs ≈14.3µs ≈2.2µs Mono4.6.2-x64 ≈54.7µs ≈12.3µs ≈3.6µs 36/39
  • 81. (Примерная схема) Сумма чисел Instruction Level Parallelism спешит на помощь! 37/39