143

Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Embed Size (px)

Citation preview

Page 1: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Page 2: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

C++ велосипедостроениедля профессионалов

Antony PolukhinПолухин Антон

Автор Boost библиотек TypeIndex, DLL, StacktraceMaintainer Boost Any, Conversion, LexicalСast, Variant

Представитель РГ21, national body

Page 3: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

О "велосипедах"

Page 4: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Зачем?

4 / 143

Page 5: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Зачем?

Для самообучения

5 / 143

Page 6: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Зачем?

Для самообучения

Особые требования к коду

6 / 143

Page 7: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Зачем?

Для самообучения

Особые требования к коду

Можем написать лучше

7 / 143

Page 8: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Зачем?

Для самообучения

Особые требования к коду

Можем написать лучше

Кажется что можем написать лучше

8 / 143

Page 9: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Зачем?

Для самообучения

Особые требования к коду

Можем написать лучше

Кажется что можем написать лучше

???

9 / 143

Page 10: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

О чём поговорим

Устаревшие технологий

Современные технологии

Оптимизациях и “вредных” бенчмарках

Новейших практиках C++ программирования

Что делать с “идеальным” велосипедом

10 / 143

Page 11: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

С какого класса начнём?

Page 12: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

std::string

Page 13: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: c чего всё начиналось

class string {

char* data;

size_t size;

size_t capacity;

// ...

};

13 / 143

Page 14: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: C++98

class string {

char* data;

// ...

public:

string(const string& s)

: data(s.clone()) // new + memmove

{}

// ...

string(const char* s); // new + memmove

};

14 / 143

Page 15: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: C++98

std::map<string, int> m;

m.insert(

std::pair<string, int>("Hello!", 1)

);

15 / 143

Page 16: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: C++98 проблемы

std::map<string, int> m;

m.insert(

std::pair<string, int>("Hello!", 1) // string("Hello!")

);

16 / 143

Page 17: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: C++98 проблемы

std::map<string, int> m;

m.insert(

std::pair<string, int>("Hello!", 1) // make_pair(string("Hello!"), 1)

);

17 / 143

Page 18: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: C++98 проблемы

std::map<string, int> m;

m.insert(

std::pair<string, int>("Hello!", 1) // m.insert(make_pair(string("Hello!"), 1))

);

18 / 143

Page 19: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: C++98 проблемы

std::map<string, int> m;

m.insert(

std::pair<string, int>("Hello!", 1) // (new + memmove) * 2 + delete

); // new + memmove + delete

19 / 143

Page 20: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: C++98 проблемы

std::map<string, int> m;

m.insert(

std::pair<string, int>("Hello!", 1) // копии котрые не надо копировать

); // копии котрые не надо копировать

20 / 143

Page 21: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: COW

class string_impl {

char* data;

size_t size;

size_t capacity;

size_t use_count; // <===

// ...

};

21 / 143

Page 22: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: COW

class string {

string_impl* impl;

public:

string(const string& s)

: impl(s.impl)

{

++ impl->use_count;

}

};

22 / 143

Page 23: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: COW

class string {

string_impl* impl;

public:

char& operator[](size_t i) {

if (impl->use_count > 1)

*this = clone();

}

return impl->data[i];

}

};

23 / 143

Page 24: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: COW

class string {

string_impl* impl;

public:

~string() {

--impl->use_count;

if (!impl->use_count) {

delete impl;

}

}

};

24 / 143

Page 25: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: COW

std::map<string, int> m;

m.insert(

std::pair<string, int>("Hello!", 1)

);

25 / 143

Page 26: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string + COW: C++98

std::map<string, int> m;

m.insert(

std::pair<string, int>("Hello!", 1) // string("Hello!")

);

26 / 143

Page 27: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string + COW: C++98

std::map<string, int> m;

m.insert(

std::pair<string, int>("Hello!", 1) // make_pair(string("Hello!"), 1)

);

27 / 143

Page 28: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string + COW: C++98

std::map<string, int> m;

m.insert(

std::pair<string, int>("Hello!", 1) // m.insert(make_pair(string("Hello!"), 1))

);

28 / 143

Page 29: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string + COW: C++98

std::map<string, int> m;

m.insert(

std::pair<string, int>("Hello!", 1)

); // 1 new + 1 memmove

29 / 143

Page 30: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

COW более чем в 2 раза ускоряет код в C++98!

Page 31: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Но...

Page 32: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Но... 2002, 2005

Page 33: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

2002, 2005

Hyper-threading в процессорах Pentium 4.

2-ядерный процессор Opteron архитектуры AMD64, предназначенный для серверов.

Pentium D x86-64 – первый 2-ядерный процессором для персональных компьютеров.

33 / 143

Page 34: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: COW MT fixes

class string_impl {

char* data;

size_t size;

size_t capacity;

size_t use_count; // <=== Ooops!

// ...

};

34 / 143

Page 35: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: COW MT fixes

class string_impl {

char* data;

size_t size;

size_t capacity;

atomic<size_t> use_count; // <=== Fixed

// ...

};

35 / 143

Page 36: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: COW MT fixes

class string {

string_impl* impl;

public:

string(const string& s)

: impl(s.impl)

{

++ impl->use_count; // atomic

}

};

36 / 143

Page 37: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: COW MT fixes

class string {

string_impl* impl;

public:

~string() {

size_t val = --impl->use_count; // atomic

if (!val) {

delete impl;

}

}

};

37 / 143

Page 38: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: COW MT fixes

class string {

string_impl* impl;

public:

char& operator[](size_t i) {

if (impl->use_count > 1) { // atomic

string cloned = clone(); // new + memmove + atomic --

swap(*this, cloned);

// atomic in ~string for cloned; delete in ~string if other thread released

the string

} // ...38 / 143

Page 39: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Чем плохи atomic?

Не атомарный INC – 1 такт [1]

[1] http://www.agner.org/optimize/instruction_tables.pdf

39 / 143

Page 40: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Чем плохи atomic?

Не атомарный INC – 1 такт [1]

Атомарная операция на одном ядре [2]:

~5 - 20+ тактов, если это ядро "владеет" атомиком

[1] http://www.agner.org/optimize/instruction_tables.pdf

[2] https://htor.inf.ethz.ch/publications/img/atomic-bench.pdf

40 / 143

Page 41: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Чем плохи atomic?

Не атомарный INC – 1 такт [1]

Атомарная операция на одном ядре [2]:

~5 - 20+ тактов, если это ядро "владеет" атомиком

~40 тактов, если другое ядро "владеет" атомиком

[1] http://www.agner.org/optimize/instruction_tables.pdf

[2] https://htor.inf.ethz.ch/publications/img/atomic-bench.pdf

41 / 143

Page 42: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Чем плохи atomic? (часть 1)

Не атомарный INC – 1 такт [1]

Атомарная операция на одном ядре [2]:

~5 - 20+ тактов, если это ядро "владеет" атомиком

~40 тактов, если другое ядро "владеет" атомиком

Несколько атомарных операций на разных ядрах над одним atomic:

retry later [3]

[1] http://www.agner.org/optimize/instruction_tables.pdf

[2] https://htor.inf.ethz.ch/publications/img/atomic-bench.pdf

[3] https://en.wikipedia.org/wiki/MESI_protocol42 / 143

Page 43: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

atomic (часть 1, макс. простой ядра)

43 / 143

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 160

100

200

300

400

500

600

700

# cores

ma

x co

re d

ela

y

Page 44: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

atomic (часть 1, суммарный простой ядер)

44 / 143

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 160

1000

2000

3000

4000

5000

6000

# cores

Tota

l de

lay

Page 45: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Чем плохи atomic (часть 2)

Компиляторы не очень умеют их оптимизировать.

Full barrier для некоторых компиляторов.

Накладывают дополнительные ограничения на близлежащие оптимизации.

https://gcc.gnu.org/wiki/Atomic/GCCMM/Optimizations

http://llvm.org/docs/Atomics.html

45 / 143

Page 46: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

COW более чем в 2 раза ускоряет код в C++98!

Page 47: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

С++11

Page 48: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: С++11 and COW

Временные объекты и без COW оптимизируются в C++11

string::string(string&& s) { swap(*this, s); }

48 / 143

Page 49: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: С++11 and COW

Временные объекты и без COW оптимизируются в C++11

std::map<string, int> m;

m.insert( std::pair<string, int>("Hello!", 1) // string("Hello!"));

49 / 143

Page 50: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: С++11 and COW

Временные объекты и без COW оптимизируются в C++11

std::map<string, int> m;

m.insert( std::pair<string, int>("Hello!", 1) // pair(string&&, int));

50 / 143

Page 51: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: С++11 and COW

Временные объекты и без COW оптимизируются в C++11

std::map<string, int> m;

m.insert( std::pair<string, int>("Hello!", 1) // m.insert(pair&&));

51 / 143

Page 52: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: С++11 and COW

Временные объекты и без COW оптимизируются в C++11

std::map<string, int> m;

m.insert( std::pair<string, int>("Hello!", 1) ); // 1 new + 1 memmove

52 / 143

Page 53: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: С++11 and COW

Временные объекты и без COW оптимизируются в C++11

В C++11 если мы копируем объект, то скорее всего мы его будем менять (в остальных случаях rvalue + RVO + NRVO)

53 / 143

Page 54: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: С++11 and COW

Временные объекты и без COW оптимизируются в C++11

В C++11 если мы копируем объект, то скорее всего мы его будем менять (в остальных случаях rvalue + RVO + NRVO)

Без COW: new + memmove

COW: atomic + atomic(x?) + new + memmove

54 / 143

Page 55: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: С++11 and COW

Временные объекты и без COW оптимизируются в C++11

В C++11 если мы копируем объект, то скорее всего мы его будем менять (в остальных случаях rvalue + RVO + NRVO)

COW вызывает atomic удручающе часто на некоторых операциях

char& operator[](size_t i) { if (impl->use_count > 1) { // atomic string cloned = clone(); // atomic-- swap(*this, cloned); // atomic in ~string for cloned } return impl->data[i];}

55 / 143

Page 56: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: С++11 and COW

Временные объекты и без COW оптимизируются в C++11

В C++11 если мы копируем объект, то скорее всего мы его будем менять (в остальных случаях rvalue + RVO + NRVO)

COW вызывает atomic удручающе часто на некоторых операциях

COW конструктор копирования использует atomic

COW деструктор COW строки вызывает atomic

56 / 143

Page 57: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

string: С++11 and COW

Временные объекты и без COW оптимизируются в C++11

В C++11 если мы копируем объект, то скорее всего мы его будем менять (в остальных случаях rvalue + RVO + NRVO)

COW вызывает atomic удручающе часто на некоторых операциях

COW конструктор копирования использует atomic

COW деструктор COW строки вызывает atomic

Итого:

С COW мы получаем множество дополнительных операций над атомиками

57 / 143

Page 58: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

COW устарел!Осторожнее с использованием.

Page 59: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

COW legacy

class bimap {

std::map<string, int> str_to_int;

std::map<int, string> int_to_str;

public:

void insert(string s, int i) {

str_to_int.insert(make_pair(s, i));

int_to_str.insert(make_pair(i, std::move(s)));

}

59 / 143

Page 60: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

COW legacy

class bimap {

std::map<string, int> str_to_int;

std::map<int, string> int_to_str;

public:

void insert(string s, int i) {

str_to_int.insert(make_pair(s, i));

int_to_str.insert(make_pair(i, std::move(s)));

}

60 / 143

Page 61: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

COW legacy

class bimap {

std::map<string, int> str_to_int;

std::map<int, string> int_to_str;

public:

void insert(string s, int i) {

str_to_int.insert(make_pair(s, i));

int_to_str.insert(make_pair(i, std::move(s)));

}

61 / 143

Page 62: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

COW legacy fix

class bimap {

std::map<string, int> str_to_int;

std::map<int, string_view> int_to_str;

public:

void insert(string s, int i) {

auto it = str_to_int.insert(make_pair(std::move(s), i));

int_to_str.insert(make_pair(i, *it.first)));

}

62 / 143

Page 63: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Одна оптимизация для string

Page 64: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Одна оптимизация для string

std::map<string, int> m;

m.insert(

std::pair<string, int>("Hello!", 1)

); // 1 new + 1 memmove

64 / 143

Page 65: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Одна оптимизация для string

class string {

char* data;

size_t size;

size_t capacity;

// ...

};

65 / 143

Page 66: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Одна оптимизация для string

class string {

size_t capacity;

union {

struct no_small_buffer_t {

char* data;

size_t size;

} no_small_buffer;

struct small_buffer_t {

char data[sizeof(no_small_buffer_t)];

} small_buffer;

} impl;

};

66 / 143

Page 67: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Одна оптимизация для string

class string {

size_t capacity;

union {

struct no_small_buffer_t {

char* data;

size_t size;

} no_small_buffer;

struct small_buffer_t {

char data[sizeof(no_small_buffer_t)];

} small_buffer;

} impl;

};

67 / 143

Page 68: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Одна оптимизация для string

class string {

size_t capacity; // == 0

union {

struct no_small_buffer_t {

char* data;

size_t size;

} no_small_buffer;

struct small_buffer_t {

char data[sizeof(no_small_buffer_t)];

} small_buffer;

} impl;

};

68 / 143

Page 69: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Одна оптимизация для string

class string {

size_t capacity; // == 0

union {

struct no_small_buffer_t {

char* data;

size_t size;

} no_small_buffer;

struct small_buffer_t {

char data[sizeof(no_small_buffer_t)]; // <=======

} small_buffer;

} impl;

};

69 / 143

Page 70: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Одна оптимизация для string

class string {

size_t capacity; // != 0

union {

struct no_small_buffer_t {

char* data;

size_t size;

} no_small_buffer;

struct small_buffer_t {

char data[sizeof(no_small_buffer_t)];

} small_buffer;

} impl;

};

70 / 143

Page 71: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Одна оптимизация для string

class string {

size_t capacity; // != 0

union {

struct no_small_buffer_t {

char* data; // <=======

size_t size;

} no_small_buffer;

struct small_buffer_t {

char data[sizeof(no_small_buffer_t)];

} small_buffer;

} impl;

};

71 / 143

Page 72: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Одна оптимизация для string

std::map<string, int> m;

m.insert(

std::pair<string, int>("Hello!", 1)

); // 1 memmove

72 / 143

Page 73: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Разработка без оглядки на готовые решения

Page 74: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

std::variant

std::variant<T...> - класс который хранит один из типов T, и помнит тип данных:

union { T0 v0; T1 v1; T2 v2; // ...};

int t_index;

74 / 143

Page 75: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Кто видит ошибку?

template <class... T>

class variant {

// ...

std::tuple<T...> data;

};

75 / 143

Page 76: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Кто видит ошибку?

variant<T...>& operator=(const U& value) {

if (&value == this) {

return *this;

}

destroy(); // data->~Ti()

construct_value(value); // new (data) Tj(value);

t_index = get_index_from_type<U>(); // t_index = j;

return *this;

}76 / 143

Page 77: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Кто видит ошибку?

variant<T...>& operator=(const U& value) {

if (&value == this) {

return *this;

}

destroy(); // data->~Ti()

construct_value(value); // throw; ...

// ...

// ~variant() {

// data->~Ti() // Oops!!!

77 / 143

Page 78: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

FORCE INLINE

Page 79: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

У процессора есть не только кеш данных

Большинство современных микропроцессоров для компьютеров и серверов имеют как минимум три независимых кэша: кэш инструкций для ускорения загрузки машинного кода, кэш данных для ускорения чтения и записи данных и буфер ассоциативной трансляции (TLB) для ускорения трансляции виртуальных (логических) адресов в физические, как для инструкций, так и для данных. [1]

[1] https://ru.wikipedia.org/wiki/Кэш_процессора

79 / 143

Page 80: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Кеш инструкций здоровой программы

Sed ut perspiciatis, unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?

80 / 143

Page 81: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Кеш инструкций с FORCE_INLINE

Sed ut perspiciatis, unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totFORCE_INLINE rem aperiFORCE_INLINE eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsFORCE_INLINE voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquFORCE_INLINE est, qui dolorem ipsum, quia dolor sit, FORCE_INLINEet, consectetur, adipisci velit, sed quia non numquFORCE_INLINE eius modi tempora incidunt, ut labore et dolore magnFORCE_INLINE aliquFORCE_INLINE quaerat voluptatem. Ut enim ad minima veniFORCE_INLINE, quis nostrum exercitationem ullFORCE_INLINE corporis suscipit laboriosFORCE_INLINE, nisi ut aliquid ex ea commodi consequatur?

81 / 143

Page 82: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Много бенчмарков игнорируют 2 кеша из 3х

Бенчмарк должен:

в бинарном виде занимать столько же, сколько ваш production код

иметь приблизительно такое же количество функций и переходов, как и ваш production код

82 / 143

Page 83: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

True story

Убрав часть шаблонных параметров из B-Tree и ощутимо уменьшив размер бинарника, получили двукратный прирост скорости.

83 / 143

Page 84: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Мнение эксперта

Understanding Compiler Optimization - Chandler Carrut: ”To get an interesting example <on inlining that hurts performance> it has to be big <...>”[1]

"Tuning C++: Benchmarks, and CPUs, and Compilers! Oh My! - Chandler Carrut: “Primary reason why modern processors are slow is due to cache misses. <...> One of those caches is actually a cache that feeds your program to the CPU. <...> When you skip over instructions, you may skip over the cache line <...> and you stall” [2]

[1] https://www.youtube.com/watch?v=FnGCDLhaxKU

[2] https://www.youtube.com/watch?v=nXaxk27zwlk84 / 143

Page 85: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Aliasing

Page 86: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Aliasing

Можно встретить рекомендации:

использовать -fno-strict-aliasing чтобы получать меньше проблем.

86 / 143

Page 87: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Aliasing

bar qwe(foo& f, const bar& b) {

f += b;

return b * b;

}

87 / 143

Page 88: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Aliasing

bar qwe(foo& f, const bar& b) {

// load b

// load f

f += b;

// load b

return b * b;

}

88 / 143

Page 89: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Aliasing

bar qwe(foo& f, const bar& b) {

// load b

// load f

f += b; // &f == &b ???

// load b

return b * b;

}

89 / 143

Page 90: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Aliasing

5% - 30% Speedup

http://docs.lib.purdue.edu/cgi/viewcontent.cgi?article=1124&context=ecetr

https://habrahabr.ru/post/114117/

90 / 143

Page 91: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Советы на каждый день

Page 92: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

vector

auto foo() {

std::vector< std::shared_ptr<int> > res;

for (unsigned i = 0; i < 1000; ++i)

res.push_back(

std::shared_ptr<int>(new int)

);

return res;

}92 / 143

Page 93: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

vector

auto foo() {

std::vector< std::shared_ptr<int> > res;

res.reserve(1000); // <======

for (unsigned i = 0; i < 1000; ++i)

res.push_back(

std::shared_ptr<int>(new int)

);

return res;

}93 / 143

Page 94: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

make_shared

auto foo() {

std::vector< std::shared_ptr<int> > res;

res.reserve(1000);

for (unsigned i = 0; i < 1000; ++i)

res.push_back(

std::make_shared<int>() // <======

);

return res;

}94 / 143

Page 95: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

for (auto v: data)

auto foo() {

std::vector<std::string> data = get_data();

// ...

for (auto v: data)

res.insert(

v

);

return res;

}

95 / 143

Page 96: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

for (auto v: data)

auto foo() {

std::vector<std::string> data = get_data();

// ...

for (auto v: data)

res.insert(

std::move(v) // <======

);

return res;

}

96 / 143

Page 97: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

for (auto&& v: data)

auto foo() {

std::vector<std::string> data = get_data();

// ...

for (auto&& v: data) // <======

res.insert(

std::move(v)

);

return res;

}

97 / 143

Page 98: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

vector

auto foo() {

std::vector<std::string> data = get_data();

// ...

std::string res = data.back();

data.pop_back();

// ...

return res;

}

98 / 143

Page 99: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

vector

auto foo() {

std::vector<std::string> data = get_data();

// ...

std::string res = std::move(data.back()); // <======

data.pop_back();

// ...

return res;

}

99 / 143

Page 100: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Global constants

#include <vector>

#include <string>

static const std::vector<std::string> CONSTANTS = {

"Hello",

"Word",

"!"

};

100 / 143

Page 101: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Global constants

#include <string_view>

static const std::string_view CONSTANTS[] = {

"Hello",

"Word",

"!"

};

101 / 143

Page 102: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Global constants

#include <string_view>

constexpr std::string_view CONSTANTS[] = {

"Hello",

"Word",

"!"

};

102 / 143

Page 103: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Inheritance

// base.hpp

struct base {

// ...

virtual void act() = 0;

virtual ~base(){}

};

103 / 143

Page 104: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Inheritance

// something.cpp

struct some_implementation: base {

// ...

void act() { /* ... */ }

};

104 / 143

Page 105: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Inheritance

// something.cpp

struct some_implementation: base {

// ...

void act() override { /* ... */ } // <======

};

105 / 143

Page 106: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Inheritance

// something.cpp

struct some_implementation final: base { // <======

// ...

void act() override { /* ... */ }

};

106 / 143

Page 107: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Inheritance

// something.cpp

namespace { // <======

struct some_implementation final: base {

// ...

void act() override { /* ... */ }

};

} // anonymous namespace

107 / 143

Page 108: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Move constructors

class my_data_struct {

std::vector<int> data_;

public:

// ...

my_data_struct(my_data_struct&& other)

: data_(other.data_)

{}

};

108 / 143

Page 109: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Move constructors

class my_data_struct {

std::vector<int> data_;

public:

// ...

my_data_struct(my_data_struct&& other)

: data_(std::move(other.data_)) // <======

{}

};

109 / 143

Page 110: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Move constructors

class my_data_struct {

std::vector<int> data_;

public:

// ...

my_data_struct(my_data_struct&& other) noexcept // <======

: data_(std::move(other.data_))

{}

};

110 / 143

Page 111: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Move constructors

class my_data_struct {

std::vector<int> data_;

public:

// ...

my_data_struct(my_data_struct&& ) = default; // <======

};

111 / 143

Page 112: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Caching

Page 113: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Caching divs

// .h

extern const double fractions[256];

// .cpp

const double fractions[256] = { 1.0 / i, ... };

113 / 143

Page 114: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Caching divs

2 cycles - MOV r32/64,m

=> 5 cycles - L1 Data Cache Latency for complex address calcs (p[n])

12 cycles - L2 Cache Latency

=> 22-29 cycles - DIV r32

36-43 cycles - L3 Cache Latency

114 / 143

Page 115: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Caching divs

5 cycles vs 22-29 cycles

115 / 143

Page 116: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Caching divs

5 cycles vs 22-29 cycles

x2-x4 faster

116 / 143

Page 117: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Caching divs

5 cycles vs 22-29 cycles

x2-x4 faster

WOW!!!

117 / 143

Page 118: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Caching divs

5 cycles vs 22-29 cycles

x2-x4 faster

WOW!!!

Чё правда?

118 / 143

Page 119: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Caching divs (недостатки)

Оптимизатор не видит значения

119 / 143

Page 120: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Caching divs (недостатки)

Оптимизатор не видит значения:

нет возможности оптимизировать ближайшие инструкции

120 / 143

Page 121: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Caching divs (недостатки)

Оптимизатор не видит значения:

нет возможности оптимизировать ближайшие инструкции

нет возможности constexpr

121 / 143

Page 122: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Caching divs (недостатки)

Оптимизатор не видит значения:

нет возможности оптимизировать ближайшие инструкции

нет возможности constexpr

aliasing (ссылка на double может указывать на ту же ячейку памяти)

122 / 143

Page 123: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Caching divs (недостатки)

Серьёзное негативное влияние на производительность:

нет возможности оптимизировать ближайшие инструкции

нет возможности constexpr

aliasing (ссылка на double может указывать на ту же ячейку памяти)

123 / 143

Page 124: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Caching divs (недостатки)

Серьёзное негативное влияние на производительность:

нет возможности оптимизировать ближайшие инструкции

нет возможности constexpr

aliasing (ссылка на double может указывать на ту же ячейку памяти)

Мы потратили кучу времени на обсуждение этого...

124 / 143

Page 125: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Caching divs (недостатки)

Серьёзное негативное влияние на производительность:

нет возможности оптимизировать ближайшие инструкции

нет возможности constexpr

aliasing (ссылка на double может указывать на ту же ячейку памяти)

Мы потратили кучу времени на обсуждение этого...

Потратили 1Kb L1 кеша из 16/32Kb

111 / 143

Page 126: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Caching divs (недостатки)

Потратили 1Kb L1 кеша из 16/32Kb

"Зачастую, производительность системы ограничена не скоростью процессора, а производительностью подсистем памяти. В то время, как традиционно компиляторы уделяли большее внимание оптимизации работы процессора, сегодня больший упор следует делать на более эффективное использование иерархии памяти." [1]

[1] Ахо, Сети, Ульман. Компиляторы. Принципы, технологии, инструменты. 2ed.2008

126 / 143

Page 127: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Caching divs (недостатки)

Потратили 1Kb L1 кеша из 16/32Kb

x10 прирост производительности без изменения инструкций [1] => Кеш очень важен

[1] Ulrich Drepper. What Every Programmer Should Know About Memory. 2007

127 / 143

Page 128: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Идаельный велосипед

Page 129: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Велосипед должен быть

Кроссплатформенный

129 / 143

Page 130: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Велосипед должен быть

Кроссплатформенный

Быстрый

130 / 143

Page 131: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Велосипед должен быть

Кроссплатформенный

Быстрый

Полезный

143 / 143

Page 132: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Велосипед должен быть

Кроссплатформенный

Быстрый

Полезный

Функциональный

132 / 143

Page 133: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

РГ21

https://stdcpp.ru/proposals

133 / 143

Page 134: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

РГ21

134 / 143

https://stdcpp.ru/proposals

Поможем с формальностями

Поможем с принятием в Boost

Поможем с написанием proposal

Защитим ваши интересы на заседнии, передадим отзывы

Page 135: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

РГ21. Идеи в разработке:

135 / 143

Page 136: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

РГ21. Идеи в разработке:

136 / 143

P0539R0: wide_int by Игорь Клеванец

Page 137: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

РГ21. Идеи в разработке:

137 / 143

P0539R0: wide_int by Игорь Клеванец

P0275R1: Dynamic library load

Page 138: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

РГ21. Идеи в разработке:

138 / 143

P0539R0: wide_int by Игорь Клеванец

P0275R1: Dynamic library load

ConcurrentHashMap by Сергей Мурылев, Антон Малахов

Page 139: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

РГ21. Идеи в разработке:

139 / 143

P0539R0: wide_int by Игорь Клеванец

P0275R1: Dynamic library load

ConcurrentHashMap by Сергей Мурылев, Антон Малахов

Constexpr: “D0202R2: Constexpr algorithm”, std::array, <numeric>, …

Page 140: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

РГ21. Идеи в разработке:

140 / 143

P0539R0: wide_int by Игорь Клеванец

P0275R1: Dynamic library load

ConcurrentHashMap by Сергей Мурылев, Антон Малахов

Constexpr: “D0202R2: Constexpr algorithm”, std::array, <numeric>, …

?dlsym?

Page 141: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

РГ21. Идеи в разработке:

141 / 143

P0539R0: wide_int by Игорь Клеванец

P0275R1: Dynamic library load

ConcurrentHashMap by Сергей Мурылев, Антон Малахов

Constexpr: “D0202R2: Constexpr algorithm”, std::array, <numeric>, …

?dlsym?

Stacktrace

Page 142: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

РГ21. Идеи в разработке:

142 / 143

P0539R0: wide_int by Игорь Клеванец

P0275R1: Dynamic library load

ConcurrentHashMap by Сергей Мурылев, Антон Малахов

Constexpr: “D0202R2: Constexpr algorithm”, std::array, <numeric>, …

?dlsym?

Stacktrace

100500 мелочей, которые делают все в ISO WG21

Page 143: Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов

Спасибо! Вопросы?

https://stdcpp.ru/

143 / 143