Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
C++ велосипедостроение
для профессионалов
Antony Polukhin
Полухин Антон
Автор Boost библиотек TypeIndex, DLL, Stacktrace
M...
О "велосипедах"
Зачем?
4 / 143
Зачем?
Для самообучения
5 / 143
Зачем?
Для самообучения
Особые требования к коду
6 / 143
Зачем?
Для самообучения
Особые требования к коду
Можем написать лучше
7 / 143
Зачем?
Для самообучения
Особые требования к коду
Можем написать лучше
Кажется что можем написать лучше
8 / 143
Зачем?
Для самообучения
Особые требования к коду
Можем написать лучше
Кажется что можем написать лучше
???
9 / 143
О чём поговорим
Устаревшие технологий
Современные технологии
Оптимизациях и “вредных” бенчмарках
Новейших практиках C++ пр...
С какого класса начнём?
std::string
string: c чего всё начиналось
class string {
char* data;
size_t size;
size_t capacity;
// ...
};
13 / 143
string: C++98
class string {
char* data;
// ...
public:
string(const string& s)
: data(s.clone()) // new + memmove
{}
// ....
string: C++98
std::map<string, int> m;
m.insert(
std::pair<string, int>("Hello!", 1)
);
15 / 143
string: C++98 проблемы
std::map<string, int> m;
m.insert(
std::pair<string, int>("Hello!", 1) // string("Hello!")
);
16 / ...
string: C++98 проблемы
std::map<string, int> m;
m.insert(
std::pair<string, int>("Hello!", 1) // make_pair(string("Hello!"...
string: C++98 проблемы
std::map<string, int> m;
m.insert(
std::pair<string, int>("Hello!", 1) // m.insert(make_pair(string...
string: C++98 проблемы
std::map<string, int> m;
m.insert(
std::pair<string, int>("Hello!", 1) // (new + memmove) * 2 + del...
string: C++98 проблемы
std::map<string, int> m;
m.insert(
std::pair<string, int>("Hello!", 1) // копии котрые не надо копи...
string: COW
class string_impl {
char* data;
size_t size;
size_t capacity;
size_t use_count; // <===
// ...
};
21 / 143
string: COW
class string {
string_impl* impl;
public:
string(const string& s)
: impl(s.impl)
{
++ impl->use_count;
}
};
22...
string: COW
class string {
string_impl* impl;
public:
char& operator[](size_t i) {
if (impl->use_count > 1)
*this = clone(...
string: COW
class string {
string_impl* impl;
public:
~string() {
--impl->use_count;
if (!impl->use_count) {
delete impl;
...
string: COW
std::map<string, int> m;
m.insert(
std::pair<string, int>("Hello!", 1)
);
25 / 143
string + COW: C++98
std::map<string, int> m;
m.insert(
std::pair<string, int>("Hello!", 1) // string("Hello!")
);
26 / 143
string + COW: C++98
std::map<string, int> m;
m.insert(
std::pair<string, int>("Hello!", 1) // make_pair(string("Hello!"), ...
string + COW: C++98
std::map<string, int> m;
m.insert(
std::pair<string, int>("Hello!", 1) // m.insert(make_pair(string("H...
string + COW: C++98
std::map<string, int> m;
m.insert(
std::pair<string, int>("Hello!", 1)
); // 1 new + 1 memmove
29 / 143
COW более чем в 2 раза ускоряет
код в C++98!
Но...
Но... 2002, 2005
2002, 2005
Hyper-threading в процессорах Pentium 4.
2-ядерный процессор Opteron архитектуры AMD64, предназначенный для сер...
string: COW MT fixes
class string_impl {
char* data;
size_t size;
size_t capacity;
size_t use_count; // <=== Ooops!
// ......
string: COW MT fixes
class string_impl {
char* data;
size_t size;
size_t capacity;
atomic<size_t> use_count; // <=== Fixed...
string: COW MT fixes
class string {
string_impl* impl;
public:
string(const string& s)
: impl(s.impl)
{
++ impl->use_count...
string: COW MT fixes
class string {
string_impl* impl;
public:
~string() {
size_t val = --impl->use_count; // atomic
if (!...
string: COW MT fixes
class string {
string_impl* impl;
public:
char& operator[](size_t i) {
if (impl->use_count > 1) { // ...
Чем плохи atomic?
Не атомарный INC – 1 такт [1]
[1] http://www.agner.org/optimize/instruction_tables.pdf
39 / 143
Чем плохи atomic?
Не атомарный INC – 1 такт [1]
Атомарная операция на одном ядре [2]:
~5 - 20+ тактов, если это ядро "влад...
Чем плохи atomic?
Не атомарный INC – 1 такт [1]
Атомарная операция на одном ядре [2]:
~5 - 20+ тактов, если это ядро "влад...
Чем плохи atomic? (часть 1)
Не атомарный INC – 1 такт [1]
Атомарная операция на одном ядре [2]:
~5 - 20+ тактов, если это ...
atomic (часть 1, макс. простой ядра)
43 / 143
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
0
100
200
300
400
500
600
700
# cores...
atomic (часть 1, суммарный простой ядер)
44 / 143
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
0
1000
2000
3000
4000
5000
6000
#...
Чем плохи atomic (часть 2)
Компиляторы не очень умеют их оптимизировать.
Full barrier для некоторых компиляторов.
Накладыв...
COW более чем в 2 раза ускоряет
код в C++98!
С++11
string: С++11 and COW
Временные объекты и без COW оптимизируются в C++11
string::string(string&& s) {
swap(*this, s);
}
48...
string: С++11 and COW
Временные объекты и без COW оптимизируются в C++11
std::map<string, int> m;
m.insert(
std::pair<stri...
string: С++11 and COW
Временные объекты и без COW оптимизируются в C++11
std::map<string, int> m;
m.insert(
std::pair<stri...
string: С++11 and COW
Временные объекты и без COW оптимизируются в C++11
std::map<string, int> m;
m.insert(
std::pair<stri...
string: С++11 and COW
Временные объекты и без COW оптимизируются в C++11
std::map<string, int> m;
m.insert(
std::pair<stri...
string: С++11 and COW
Временные объекты и без COW оптимизируются в C++11
В C++11 если мы копируем объект, то скорее всего ...
string: С++11 and COW
Временные объекты и без COW оптимизируются в C++11
В C++11 если мы копируем объект, то скорее всего ...
string: С++11 and COW
Временные объекты и без COW оптимизируются в C++11
В C++11 если мы копируем объект, то скорее всего ...
string: С++11 and COW
Временные объекты и без COW оптимизируются в C++11
В C++11 если мы копируем объект, то скорее всего ...
string: С++11 and COW
Временные объекты и без COW оптимизируются в C++11
В C++11 если мы копируем объект, то скорее всего ...
COW устарел!
Осторожнее с использованием.
COW legacy
class bimap {
std::map<string, int> str_to_int;
std::map<int, string> int_to_str;
public:
void insert(string s,...
COW legacy
class bimap {
std::map<string, int> str_to_int;
std::map<int, string> int_to_str;
public:
void insert(string s,...
COW legacy
class bimap {
std::map<string, int> str_to_int;
std::map<int, string> int_to_str;
public:
void insert(string s,...
COW legacy fix
class bimap {
std::map<string, int> str_to_int;
std::map<int, string_view> int_to_str;
public:
void insert(...
Одна оптимизация для string
Одна оптимизация для string
std::map<string, int> m;
m.insert(
std::pair<string, int>("Hello!", 1)
); // 1 new + 1 memmove...
Одна оптимизация для string
class string {
char* data;
size_t size;
size_t capacity;
// ...
};
65 / 143
Одна оптимизация для string
class string {
size_t capacity;
union {
struct no_small_buffer_t {
char* data;
size_t size;
} ...
Одна оптимизация для string
class string {
size_t capacity;
union {
struct no_small_buffer_t {
char* data;
size_t size;
} ...
Одна оптимизация для string
class string {
size_t capacity; // == 0
union {
struct no_small_buffer_t {
char* data;
size_t ...
Одна оптимизация для string
class string {
size_t capacity; // == 0
union {
struct no_small_buffer_t {
char* data;
size_t ...
Одна оптимизация для string
class string {
size_t capacity; // != 0
union {
struct no_small_buffer_t {
char* data;
size_t ...
Одна оптимизация для string
class string {
size_t capacity; // != 0
union {
struct no_small_buffer_t {
char* data; // <===...
Одна оптимизация для string
std::map<string, int> m;
m.insert(
std::pair<string, int>("Hello!", 1)
); // 1 memmove
72 / 143
Разработка без оглядки на
готовые решения
std::variant
std::variant<T...> - класс который хранит один из типов T, и помнит тип данных:
union {
T0 v0;
T1 v1;
T2 v2;
...
Кто видит ошибку?
template <class... T>
class variant {
// ...
std::tuple<T...> data;
};
75 / 143
Кто видит ошибку?
variant<T...>& operator=(const U& value) {
if (&value == this) {
return *this;
}
destroy(); // data->~Ti...
Кто видит ошибку?
variant<T...>& operator=(const U& value) {
if (&value == this) {
return *this;
}
destroy(); // data->~Ti...
FORCE INLINE
У процессора есть не только кеш данных
Большинство современных микропроцессоров для компьютеров и серверов имеют
как миним...
Кеш инструкций здоровой программы
Sed ut perspiciatis, unde omnis iste natus error sit voluptatem
accusantium doloremque l...
Кеш инструкций с FORCE_INLINE
Sed ut perspiciatis, unde omnis iste natus error sit voluptatem
accusantium doloremque lauda...
Много бенчмарков игнорируют 2 кеша из 3х
Бенчмарк должен:
в бинарном виде занимать столько же, сколько ваш production код
...
True story
Убрав часть шаблонных параметров из B-Tree и ощутимо уменьшив размер
бинарника, получили двукратный прирост ско...
Мнение эксперта
Understanding Compiler Optimization - Chandler Carrut: ”To get an interesting example
<on inlining that hu...
Aliasing
Aliasing
Можно встретить рекомендации:
использовать -fno-strict-aliasing чтобы получать меньше проблем.
86 / 143
Aliasing
bar qwe(foo& f, const bar& b) {
f += b;
return b * b;
}
87 / 143
Aliasing
bar qwe(foo& f, const bar& b) {
// load b
// load f
f += b;
// load b
return b * b;
}
88 / 143
Aliasing
bar qwe(foo& f, const bar& b) {
// load b
// load f
f += b; // &f == &b ???
// load b
return b * b;
}
89 / 143
Aliasing
5% - 30% Speedup
http://docs.lib.purdue.edu/cgi/viewcontent.cgi?article=1124&context=ecetr
https://habrahabr.ru/p...
Советы на каждый день
vector
auto foo() {
std::vector< std::shared_ptr<int> > res;
for (unsigned i = 0; i < 1000; ++i)
res.push_back(
std::share...
vector
auto foo() {
std::vector< std::shared_ptr<int> > res;
res.reserve(1000); // <======
for (unsigned i = 0; i < 1000; ...
make_shared
auto foo() {
std::vector< std::shared_ptr<int> > res;
res.reserve(1000);
for (unsigned i = 0; i < 1000; ++i)
r...
for (auto v: data)
auto foo() {
std::vector<std::string> data = get_data();
// ...
for (auto v: data)
res.insert(
v
);
ret...
for (auto v: data)
auto foo() {
std::vector<std::string> data = get_data();
// ...
for (auto v: data)
res.insert(
std::mov...
for (auto&& v: data)
auto foo() {
std::vector<std::string> data = get_data();
// ...
for (auto&& v: data) // <======
res.i...
vector
auto foo() {
std::vector<std::string> data = get_data();
// ...
std::string res = data.back();
data.pop_back();
// ...
vector
auto foo() {
std::vector<std::string> data = get_data();
// ...
std::string res = std::move(data.back()); // <=====...
Global constants
#include <vector>
#include <string>
static const std::vector<std::string> CONSTANTS = {
"Hello",
"Word",
...
Global constants
#include <string_view>
static const std::string_view CONSTANTS[] = {
"Hello",
"Word",
"!"
};
101 / 143
Global constants
#include <string_view>
constexpr std::string_view CONSTANTS[] = {
"Hello",
"Word",
"!"
};
102 / 143
Inheritance
// base.hpp
struct base {
// ...
virtual void act() = 0;
virtual ~base(){}
};
103 / 143
Inheritance
// something.cpp
struct some_implementation: base {
// ...
void act() { /* ... */ }
};
104 / 143
Inheritance
// something.cpp
struct some_implementation: base {
// ...
void act() override { /* ... */ } // <======
};
105...
Inheritance
// something.cpp
struct some_implementation final: base { // <======
// ...
void act() override { /* ... */ }
...
Inheritance
// something.cpp
namespace { // <======
struct some_implementation final: base {
// ...
void act() override { ...
Move constructors
class my_data_struct {
std::vector<int> data_;
public:
// ...
my_data_struct(my_data_struct&& other)
: d...
Move constructors
class my_data_struct {
std::vector<int> data_;
public:
// ...
my_data_struct(my_data_struct&& other)
: d...
Move constructors
class my_data_struct {
std::vector<int> data_;
public:
// ...
my_data_struct(my_data_struct&& other) noe...
Move constructors
class my_data_struct {
std::vector<int> data_;
public:
// ...
my_data_struct(my_data_struct&& ) = defaul...
Caching
Caching divs
// .h
extern const double fractions[256];
// .cpp
const double fractions[256] = { 1.0 / i, ... };
113 / 143
Caching divs
2 cycles - MOV r32/64,m
=> 5 cycles - L1 Data Cache Latency for complex address calcs (p[n])
12 cycles - L2 C...
Caching divs
5 cycles vs 22-29 cycles
115 / 143
Caching divs
5 cycles vs 22-29 cycles
x2-x4 faster
116 / 143
Caching divs
5 cycles vs 22-29 cycles
x2-x4 faster
WOW!!!
117 / 143
Caching divs
5 cycles vs 22-29 cycles
x2-x4 faster
WOW!!!
Чё правда?
118 / 143
Caching divs (недостатки)
Оптимизатор не видит значения
119 / 143
Caching divs (недостатки)
Оптимизатор не видит значения:
нет возможности оптимизировать ближайшие инструкции
120 / 143
Caching divs (недостатки)
Оптимизатор не видит значения:
нет возможности оптимизировать ближайшие инструкции
нет возможнос...
Caching divs (недостатки)
Оптимизатор не видит значения:
нет возможности оптимизировать ближайшие инструкции
нет возможнос...
Caching divs (недостатки)
Серьёзное негативное влияние на производительность:
нет возможности оптимизировать ближайшие инс...
Caching divs (недостатки)
Серьёзное негативное влияние на производительность:
нет возможности оптимизировать ближайшие инс...
Caching divs (недостатки)
Серьёзное негативное влияние на производительность:
нет возможности оптимизировать ближайшие инс...
Caching divs (недостатки)
Потратили 1Kb L1 кеша из 16/32Kb
"Зачастую, производительность системы ограничена не скоростью
п...
Caching divs (недостатки)
Потратили 1Kb L1 кеша из 16/32Kb
x10 прирост производительности без изменения инструкций [1] => ...
Идаельный велосипед
Велосипед должен быть
Кроссплатформенный
129 / 143
Велосипед должен быть
Кроссплатформенный
Быстрый
130 / 143
Велосипед должен быть
Кроссплатформенный
Быстрый
Полезный
143 / 143
Велосипед должен быть
Кроссплатформенный
Быстрый
Полезный
Функциональный
132 / 143
РГ21
https://stdcpp.ru/proposals
133 / 143
РГ21
134 / 143
https://stdcpp.ru/proposals
Поможем с формальностями
Поможем с принятием в Boost
Поможем с написанием propo...
РГ21. Идеи в разработке:
135 / 143
РГ21. Идеи в разработке:
136 / 143
P0539R0: wide_int by Игорь Клеванец
РГ21. Идеи в разработке:
137 / 143
P0539R0: wide_int by Игорь Клеванец
P0275R1: Dynamic library load
РГ21. Идеи в разработке:
138 / 143
P0539R0: wide_int by Игорь Клеванец
P0275R1: Dynamic library load
ConcurrentHashMap by ...
РГ21. Идеи в разработке:
139 / 143
P0539R0: wide_int by Игорь Клеванец
P0275R1: Dynamic library load
ConcurrentHashMap by ...
РГ21. Идеи в разработке:
140 / 143
P0539R0: wide_int by Игорь Клеванец
P0275R1: Dynamic library load
ConcurrentHashMap by ...
РГ21. Идеи в разработке:
141 / 143
P0539R0: wide_int by Игорь Клеванец
P0275R1: Dynamic library load
ConcurrentHashMap by ...
РГ21. Идеи в разработке:
142 / 143
P0539R0: wide_int by Игорь Клеванец
P0275R1: Dynamic library load
ConcurrentHashMap by ...
Спасибо! Вопросы?
https://stdcpp.ru/
143 / 143
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Upcoming SlideShare
Loading in …5
×

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

5,796 views

Published on

В докладе перед нами откроется великолепный мир велосипедов и устаревших технологий, которые люди продолжают переносить в новые проекты и повсеместно использовать. Мы поговорим о:

Copy-On-Write
разработке без оглядки на готовые решения и к чему это приводит
force inline
оптимизациях, которые отлично себя показывают на бенчмарках и плохо себя ведут в реальной жизни
бездумно отключаемых оптимизациях компилятора
тонкостях стандартной библиотеки для повседневного использования
супер качественном велосипедостроении

Published in: Software
  • Be the first to comment

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

  1. 1. C++ велосипедостроение для профессионалов Antony Polukhin Полухин Антон Автор Boost библиотек TypeIndex, DLL, Stacktrace Maintainer Boost Any, Conversion, LexicalСast, Variant Представитель РГ21, national body
  2. 2. О "велосипедах"
  3. 3. Зачем? 4 / 143
  4. 4. Зачем? Для самообучения 5 / 143
  5. 5. Зачем? Для самообучения Особые требования к коду 6 / 143
  6. 6. Зачем? Для самообучения Особые требования к коду Можем написать лучше 7 / 143
  7. 7. Зачем? Для самообучения Особые требования к коду Можем написать лучше Кажется что можем написать лучше 8 / 143
  8. 8. Зачем? Для самообучения Особые требования к коду Можем написать лучше Кажется что можем написать лучше ??? 9 / 143
  9. 9. О чём поговорим Устаревшие технологий Современные технологии Оптимизациях и “вредных” бенчмарках Новейших практиках C++ программирования Что делать с “идеальным” велосипедом 10 / 143
  10. 10. С какого класса начнём?
  11. 11. std::string
  12. 12. string: c чего всё начиналось class string { char* data; size_t size; size_t capacity; // ... }; 13 / 143
  13. 13. 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
  14. 14. string: C++98 std::map<string, int> m; m.insert( std::pair<string, int>("Hello!", 1) ); 15 / 143
  15. 15. string: C++98 проблемы std::map<string, int> m; m.insert( std::pair<string, int>("Hello!", 1) // string("Hello!") ); 16 / 143
  16. 16. string: C++98 проблемы std::map<string, int> m; m.insert( std::pair<string, int>("Hello!", 1) // make_pair(string("Hello!"), 1) ); 17 / 143
  17. 17. 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
  18. 18. 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
  19. 19. string: C++98 проблемы std::map<string, int> m; m.insert( std::pair<string, int>("Hello!", 1) // копии котрые не надо копировать ); // копии котрые не надо копировать 20 / 143
  20. 20. string: COW class string_impl { char* data; size_t size; size_t capacity; size_t use_count; // <=== // ... }; 21 / 143
  21. 21. string: COW class string { string_impl* impl; public: string(const string& s) : impl(s.impl) { ++ impl->use_count; } }; 22 / 143
  22. 22. 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
  23. 23. string: COW class string { string_impl* impl; public: ~string() { --impl->use_count; if (!impl->use_count) { delete impl; } } }; 24 / 143
  24. 24. string: COW std::map<string, int> m; m.insert( std::pair<string, int>("Hello!", 1) ); 25 / 143
  25. 25. string + COW: C++98 std::map<string, int> m; m.insert( std::pair<string, int>("Hello!", 1) // string("Hello!") ); 26 / 143
  26. 26. string + COW: C++98 std::map<string, int> m; m.insert( std::pair<string, int>("Hello!", 1) // make_pair(string("Hello!"), 1) ); 27 / 143
  27. 27. 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
  28. 28. string + COW: C++98 std::map<string, int> m; m.insert( std::pair<string, int>("Hello!", 1) ); // 1 new + 1 memmove 29 / 143
  29. 29. COW более чем в 2 раза ускоряет код в C++98!
  30. 30. Но...
  31. 31. Но... 2002, 2005
  32. 32. 2002, 2005 Hyper-threading в процессорах Pentium 4. 2-ядерный процессор Opteron архитектуры AMD64, предназначенный для серверов. Pentium D x86-64 – первый 2-ядерный процессором для персональных компьютеров. 33 / 143
  33. 33. string: COW MT fixes class string_impl { char* data; size_t size; size_t capacity; size_t use_count; // <=== Ooops! // ... }; 34 / 143
  34. 34. string: COW MT fixes class string_impl { char* data; size_t size; size_t capacity; atomic<size_t> use_count; // <=== Fixed // ... }; 35 / 143
  35. 35. string: COW MT fixes class string { string_impl* impl; public: string(const string& s) : impl(s.impl) { ++ impl->use_count; // atomic } }; 36 / 143
  36. 36. string: COW MT fixes class string { string_impl* impl; public: ~string() { size_t val = --impl->use_count; // atomic if (!val) { delete impl; } } }; 37 / 143
  37. 37. 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
  38. 38. Чем плохи atomic? Не атомарный INC – 1 такт [1] [1] http://www.agner.org/optimize/instruction_tables.pdf 39 / 143
  39. 39. Чем плохи 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
  40. 40. Чем плохи 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
  41. 41. Чем плохи 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_protocol 42 / 143
  42. 42. atomic (часть 1, макс. простой ядра) 43 / 143 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 0 100 200 300 400 500 600 700 # cores maxcoredelay
  43. 43. atomic (часть 1, суммарный простой ядер) 44 / 143 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 0 1000 2000 3000 4000 5000 6000 # cores Totaldelay
  44. 44. Чем плохи atomic (часть 2) Компиляторы не очень умеют их оптимизировать. Full barrier для некоторых компиляторов. Накладывают дополнительные ограничения на близлежащие оптимизации. https://gcc.gnu.org/wiki/Atomic/GCCMM/Optimizations http://llvm.org/docs/Atomics.html 45 / 143
  45. 45. COW более чем в 2 раза ускоряет код в C++98!
  46. 46. С++11
  47. 47. string: С++11 and COW Временные объекты и без COW оптимизируются в C++11 string::string(string&& s) { swap(*this, s); } 48 / 143
  48. 48. string: С++11 and COW Временные объекты и без COW оптимизируются в C++11 std::map<string, int> m; m.insert( std::pair<string, int>("Hello!", 1) // string("Hello!") ); 49 / 143
  49. 49. 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
  50. 50. 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
  51. 51. 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
  52. 52. string: С++11 and COW Временные объекты и без COW оптимизируются в C++11 В C++11 если мы копируем объект, то скорее всего мы его будем менять (в остальных случаях rvalue + RVO + NRVO) 53 / 143
  53. 53. string: С++11 and COW Временные объекты и без COW оптимизируются в C++11 В C++11 если мы копируем объект, то скорее всего мы его будем менять (в остальных случаях rvalue + RVO + NRVO) Без COW: new + memmove COW: atomic + atomic(x?) + new + memmove 54 / 143
  54. 54. 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
  55. 55. string: С++11 and COW Временные объекты и без COW оптимизируются в C++11 В C++11 если мы копируем объект, то скорее всего мы его будем менять (в остальных случаях rvalue + RVO + NRVO) COW вызывает atomic удручающе часто на некоторых операциях COW конструктор копирования использует atomic COW деструктор COW строки вызывает atomic 56 / 143
  56. 56. string: С++11 and COW Временные объекты и без COW оптимизируются в C++11 В C++11 если мы копируем объект, то скорее всего мы его будем менять (в остальных случаях rvalue + RVO + NRVO) COW вызывает atomic удручающе часто на некоторых операциях COW конструктор копирования использует atomic COW деструктор COW строки вызывает atomic Итого: С COW мы получаем множество дополнительных операций над атомиками 57 / 143
  57. 57. COW устарел! Осторожнее с использованием.
  58. 58. 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
  59. 59. 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
  60. 60. 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
  61. 61. 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
  62. 62. Одна оптимизация для string
  63. 63. Одна оптимизация для string std::map<string, int> m; m.insert( std::pair<string, int>("Hello!", 1) ); // 1 new + 1 memmove 64 / 143
  64. 64. Одна оптимизация для string class string { char* data; size_t size; size_t capacity; // ... }; 65 / 143
  65. 65. Одна оптимизация для 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
  66. 66. Одна оптимизация для 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
  67. 67. Одна оптимизация для 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
  68. 68. Одна оптимизация для 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
  69. 69. Одна оптимизация для 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
  70. 70. Одна оптимизация для 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
  71. 71. Одна оптимизация для string std::map<string, int> m; m.insert( std::pair<string, int>("Hello!", 1) ); // 1 memmove 72 / 143
  72. 72. Разработка без оглядки на готовые решения
  73. 73. std::variant std::variant<T...> - класс который хранит один из типов T, и помнит тип данных: union { T0 v0; T1 v1; T2 v2; // ... }; int t_index; 74 / 143
  74. 74. Кто видит ошибку? template <class... T> class variant { // ... std::tuple<T...> data; }; 75 / 143
  75. 75. Кто видит ошибку? 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
  76. 76. Кто видит ошибку? variant<T...>& operator=(const U& value) { if (&value == this) { return *this; } destroy(); // data->~Ti() construct_value(value); // throw; ... // ... // ~variant() { // data->~Ti() // Oops!!! 77 / 143
  77. 77. FORCE INLINE
  78. 78. У процессора есть не только кеш данных Большинство современных микропроцессоров для компьютеров и серверов имеют как минимум три независимых кэша: кэш инструкций для ускорения загрузки машинного кода, кэш данных для ускорения чтения и записи данных и буфер ассоциативной трансляции (TLB) для ускорения трансляции виртуальных (логических) адресов в физические, как для инструкций, так и для данных. [1] [1] https://ru.wikipedia.org/wiki/Кэш_процессора 79 / 143
  79. 79. Кеш инструкций здоровой программы 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
  80. 80. Кеш инструкций с 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
  81. 81. Много бенчмарков игнорируют 2 кеша из 3х Бенчмарк должен: в бинарном виде занимать столько же, сколько ваш production код иметь приблизительно такое же количество функций и переходов, как и ваш production код 82 / 143
  82. 82. True story Убрав часть шаблонных параметров из B-Tree и ощутимо уменьшив размер бинарника, получили двукратный прирост скорости. 83 / 143
  83. 83. Мнение эксперта 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=nXaxk27zwlk 84 / 143
  84. 84. Aliasing
  85. 85. Aliasing Можно встретить рекомендации: использовать -fno-strict-aliasing чтобы получать меньше проблем. 86 / 143
  86. 86. Aliasing bar qwe(foo& f, const bar& b) { f += b; return b * b; } 87 / 143
  87. 87. Aliasing bar qwe(foo& f, const bar& b) { // load b // load f f += b; // load b return b * b; } 88 / 143
  88. 88. Aliasing bar qwe(foo& f, const bar& b) { // load b // load f f += b; // &f == &b ??? // load b return b * b; } 89 / 143
  89. 89. Aliasing 5% - 30% Speedup http://docs.lib.purdue.edu/cgi/viewcontent.cgi?article=1124&context=ecetr https://habrahabr.ru/post/114117/ 90 / 143
  90. 90. Советы на каждый день
  91. 91. 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
  92. 92. 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
  93. 93. 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
  94. 94. for (auto v: data) auto foo() { std::vector<std::string> data = get_data(); // ... for (auto v: data) res.insert( v ); return res; } 95 / 143
  95. 95. 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
  96. 96. 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
  97. 97. vector auto foo() { std::vector<std::string> data = get_data(); // ... std::string res = data.back(); data.pop_back(); // ... return res; } 98 / 143
  98. 98. vector auto foo() { std::vector<std::string> data = get_data(); // ... std::string res = std::move(data.back()); // <====== data.pop_back(); // ... return res; } 99 / 143
  99. 99. Global constants #include <vector> #include <string> static const std::vector<std::string> CONSTANTS = { "Hello", "Word", "!" }; 100 / 143
  100. 100. Global constants #include <string_view> static const std::string_view CONSTANTS[] = { "Hello", "Word", "!" }; 101 / 143
  101. 101. Global constants #include <string_view> constexpr std::string_view CONSTANTS[] = { "Hello", "Word", "!" }; 102 / 143
  102. 102. Inheritance // base.hpp struct base { // ... virtual void act() = 0; virtual ~base(){} }; 103 / 143
  103. 103. Inheritance // something.cpp struct some_implementation: base { // ... void act() { /* ... */ } }; 104 / 143
  104. 104. Inheritance // something.cpp struct some_implementation: base { // ... void act() override { /* ... */ } // <====== }; 105 / 143
  105. 105. Inheritance // something.cpp struct some_implementation final: base { // <====== // ... void act() override { /* ... */ } }; 106 / 143
  106. 106. Inheritance // something.cpp namespace { // <====== struct some_implementation final: base { // ... void act() override { /* ... */ } }; } // anonymous namespace 107 / 143
  107. 107. Move constructors class my_data_struct { std::vector<int> data_; public: // ... my_data_struct(my_data_struct&& other) : data_(other.data_) {} }; 108 / 143
  108. 108. 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
  109. 109. 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
  110. 110. Move constructors class my_data_struct { std::vector<int> data_; public: // ... my_data_struct(my_data_struct&& ) = default; // <====== }; 111 / 143
  111. 111. Caching
  112. 112. Caching divs // .h extern const double fractions[256]; // .cpp const double fractions[256] = { 1.0 / i, ... }; 113 / 143
  113. 113. 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
  114. 114. Caching divs 5 cycles vs 22-29 cycles 115 / 143
  115. 115. Caching divs 5 cycles vs 22-29 cycles x2-x4 faster 116 / 143
  116. 116. Caching divs 5 cycles vs 22-29 cycles x2-x4 faster WOW!!! 117 / 143
  117. 117. Caching divs 5 cycles vs 22-29 cycles x2-x4 faster WOW!!! Чё правда? 118 / 143
  118. 118. Caching divs (недостатки) Оптимизатор не видит значения 119 / 143
  119. 119. Caching divs (недостатки) Оптимизатор не видит значения: нет возможности оптимизировать ближайшие инструкции 120 / 143
  120. 120. Caching divs (недостатки) Оптимизатор не видит значения: нет возможности оптимизировать ближайшие инструкции нет возможности constexpr 121 / 143
  121. 121. Caching divs (недостатки) Оптимизатор не видит значения: нет возможности оптимизировать ближайшие инструкции нет возможности constexpr aliasing (ссылка на double может указывать на ту же ячейку памяти) 122 / 143
  122. 122. Caching divs (недостатки) Серьёзное негативное влияние на производительность: нет возможности оптимизировать ближайшие инструкции нет возможности constexpr aliasing (ссылка на double может указывать на ту же ячейку памяти) 123 / 143
  123. 123. Caching divs (недостатки) Серьёзное негативное влияние на производительность: нет возможности оптимизировать ближайшие инструкции нет возможности constexpr aliasing (ссылка на double может указывать на ту же ячейку памяти) Мы потратили кучу времени на обсуждение этого... 124 / 143
  124. 124. Caching divs (недостатки) Серьёзное негативное влияние на производительность: нет возможности оптимизировать ближайшие инструкции нет возможности constexpr aliasing (ссылка на double может указывать на ту же ячейку памяти) Мы потратили кучу времени на обсуждение этого... Потратили 1Kb L1 кеша из 16/32Kb 111 / 143
  125. 125. Caching divs (недостатки) Потратили 1Kb L1 кеша из 16/32Kb "Зачастую, производительность системы ограничена не скоростью процессора, а производительностью подсистем памяти. В то время, как традиционно компиляторы уделяли большее внимание оптимизации работы процессора, сегодня больший упор следует делать на более эффективное использование иерархии памяти." [1] [1] Ахо, Сети, Ульман. Компиляторы. Принципы, технологии, инструменты. 2ed.2008 126 / 143
  126. 126. Caching divs (недостатки) Потратили 1Kb L1 кеша из 16/32Kb x10 прирост производительности без изменения инструкций [1] => Кеш очень важен [1] Ulrich Drepper. What Every Programmer Should Know About Memory. 2007 127 / 143
  127. 127. Идаельный велосипед
  128. 128. Велосипед должен быть Кроссплатформенный 129 / 143
  129. 129. Велосипед должен быть Кроссплатформенный Быстрый 130 / 143
  130. 130. Велосипед должен быть Кроссплатформенный Быстрый Полезный 143 / 143
  131. 131. Велосипед должен быть Кроссплатформенный Быстрый Полезный Функциональный 132 / 143
  132. 132. РГ21 https://stdcpp.ru/proposals 133 / 143
  133. 133. РГ21 134 / 143 https://stdcpp.ru/proposals Поможем с формальностями Поможем с принятием в Boost Поможем с написанием proposal Защитим ваши интересы на заседнии, передадим отзывы
  134. 134. РГ21. Идеи в разработке: 135 / 143
  135. 135. РГ21. Идеи в разработке: 136 / 143 P0539R0: wide_int by Игорь Клеванец
  136. 136. РГ21. Идеи в разработке: 137 / 143 P0539R0: wide_int by Игорь Клеванец P0275R1: Dynamic library load
  137. 137. РГ21. Идеи в разработке: 138 / 143 P0539R0: wide_int by Игорь Клеванец P0275R1: Dynamic library load ConcurrentHashMap by Сергей Мурылев, Антон Малахов
  138. 138. РГ21. Идеи в разработке: 139 / 143 P0539R0: wide_int by Игорь Клеванец P0275R1: Dynamic library load ConcurrentHashMap by Сергей Мурылев, Антон Малахов Constexpr: “D0202R2: Constexpr algorithm”, std::array, <numeric>, …
  139. 139. РГ21. Идеи в разработке: 140 / 143 P0539R0: wide_int by Игорь Клеванец P0275R1: Dynamic library load ConcurrentHashMap by Сергей Мурылев, Антон Малахов Constexpr: “D0202R2: Constexpr algorithm”, std::array, <numeric>, … ?dlsym?
  140. 140. РГ21. Идеи в разработке: 141 / 143 P0539R0: wide_int by Игорь Клеванец P0275R1: Dynamic library load ConcurrentHashMap by Сергей Мурылев, Антон Малахов Constexpr: “D0202R2: Constexpr algorithm”, std::array, <numeric>, … ?dlsym? Stacktrace
  141. 141. РГ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
  142. 142. Спасибо! Вопросы? https://stdcpp.ru/ 143 / 143

×