Anúncio

Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group

Mail.ru Group
15 de Nov de 2019
Anúncio

Mais conteúdo relacionado

Apresentações para você(20)

Similar a Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group(20)

Anúncio

Mais de Mail.ru Group(20)

Último(20)

Anúncio

Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group

  1. Кирилл Березин Ведущий программист Как не сделать врагами архитектуру и оптимизацию 14 Ноября 2019
  2. Постановка задачи ● Задача – сменить библиотеку json; ● Json использовался в качестве внутреннего формата данных; ● Использование шаблонов выражений для уменьшения кода.
  3. Зачем все это было? ● Исследование показало, что операции с json занимали не меньше 20 % времени; ● Rapidjson read rate 140 МБ/с (худший); ● Cpprest sdk 13 МБ/с.
  4. Архитектура программы l Функциональность в библиотеках; l Внутренний формат данных json-lib. dll Json wrapper dll exe
  5. Первые результаты l Результат перевода теста одной из библиотек показал: - Rapidjson 15-25 МБ/с - Cpp rest sdk 13 МБ/с
  6. Исследуем код cpprest typedef std::vector<std::pair<utility::string_t, json::value>> storage_type;
  7. Исследуем код cpprest typedef std::vector<std::pair<utility::string_t, json::value>> storage_type;
  8. Исследуем код cpprest l Используется move семантика; l Элементы могут быть сортированы – бинарный поиск; l Элементы полиморфные; l Поставляется в виде отдельной библиотеки.
  9. Исследуем код Rapidjson union Data { String s; ShortString ss; ... };
  10. Исследуем код Rapidjson l Кастомные аллокаторы; l Микрооптимизации Neon/sse; l header only – уровень оптимизации определяется основным кодом.
  11. Сравнительный тест производительности (-O3). l Nativejson benchmark (для некоторого i5 3 поколения) Benchmarking Performance of C++ REST SDK (C++11) Parse canada.json ... 162.880 ms 13.180 MB/s Parse citm_catalog.json ... 23.060 ms 71.431 MB/s Parse twitter.json ... 10.985 ms 54.826 MB/s Benchmarking Performance of RapidJSON_FullPrec (C++) Parse canada.json ... 15.184 ms 141.384 MB/s Parse citm_catalog.json ... 3.109 ms 529.813 MB/s Parse twitter.json ... 2.084 ms 288.992 MB/s
  12. Сравнительный тест производительности (-O0). l Nativejson benchmark (для некоторого i5 3 поколения) Benchmarking Performance of C++ REST SDK (C++11) Parse canada.json ... 166.016 ms 12.931 MB/s Parse citm_catalog.json ... 23.549 ms 69.947 MB/s Parse twitter.json ... 11.044 ms 54.533 MB/s Benchmarking Performance of RapidJSON_FullPrec (C++) Parse canada.json ... 139.193 ms 15.423 MB/s Parse citm_catalog.json ... 25.075 ms 65.691 MB/s Parse twitter.json ... 10.639 ms 56.609 MB/s
  13. Шаблоны выражений l Быстрая, но громозкая реализация; l Оборачиваем шаблоном, с более короткой реализацией; l Требуется оптимизация кода; l Код по скорости близок к идеальному.
  14. template<typename T> struct SArray { explicit SArray(std::size_t s) : storage(new T[s]), storage_size(s) { ... } SArray(SArray<T> const& orig) { … } ~SArray() { delete [] storage; } std::size_t size() const { return storage_size; } T const& operator[] (std::size_t idx) const { return storage[idx]; } T& operator[] (std::size_t idx) { return storage[idx]; } T* storage; std::size_t storage_size; };
  15. Низкоуровневая реализация int main() { SArray<double> x(1000), y(1000); for (auto i = 0 ; i < x.size(); ++i) x[i] = 1.2 * x[i] + x[i] * y[i]; }
  16. main: push r12 mov edi, 8000 push rbp sub rsp, 8 call operator new[](unsigned long)@PLT lea rdi, 8[rax] ... call operator new[](unsigned long)@PLT lea rdi, 8[rax] ... rep stosq Готовый код конструкторы
  17. .L2: movupd xmm3, XMMWORD PTR 0[rbp+rax] movupd xmm0, XMMWORD PTR [r8+rax] mulpd xmm0, xmm3 movapd xmm1, xmm3 mulpd xmm1, xmm2 addpd xmm0, xmm1 movups XMMWORD PTR 0[rbp+rax], xmm0 add rax, 16 cmp rax, 8000 jne .L2 mov rdi, r8 call operator delete[](void*)@PLT mov rdi, rbp call operator delete[](void*)@PLT x[i] = 1.2 * x[i] + x[i] * y[i]; Готовый код выражение.
  18. template<typename T, typename OP1, typename OP2> struct A_Mult { typename A_Traits<OP1>::ExprRef op1; // first operand typename A_Traits<OP2>::ExprRef op2; // second operand A_Mult (OP1 const& a, OP2 const& b) : op1(a), op2(b) { } T operator[] (std::size_t idx) const { return op1[idx] * op2[idx]; } std::size_t size() const { return std::max(a.size(), b.size()); } }; Шаблон выражения
  19. Скалярный тип. template<typename T> struct A_Scalar { T const& scalar; constexpr A_Scalar (T const& v) : scalar(v) { } constexpr T const& operator[] (std::size_t) const { return scalar; } constexpr std::size_t size() const { return 0; }; };
  20. Обертка. template<typename T, typename Rep = SArray<T>> struct Array { Rep expr_rep; // данные или класс-выражение explicit Array (std::size_t s) : expr_rep(s) { } Array (Rep const& rb) : expr_rep(rb) { } Array& operator= (Array const& b) { for (std::size_t idx = 0; idx<b.size(); ++idx) { expr_rep[idx] = b[idx]; } return *this; }
  21. std::size_t size() const { return expr_rep.size(); } decltype(auto) operator[] (std::size_t idx) const { return expr_rep[idx]; } T& operator[] (std::size_t idx) { return expr_rep[idx]; } }; Обертка.
  22. Оператор template<typename T, typename R1, typename R2> Array<T, A_Mult<T,R1,R2>> operator* (Array<T,R1> const& a, Array<T,R2> const& b) { return Array<T,A_Mult<T,R1,R2>> (A_Mult<T,R1,R2>(a.rep(), b.rep())); }
  23. Как используется int main() { Array<double> x(1000), y(1000); x = 1.2 * x + x * y; }
  24. Выражение Array<double, SArray<double> >& Array<double, SArray<double> >::operator=<double, A_Add<double, A_Mult<double, A_Scalar<double>, SArray<double> >, A_Mult<double, SArray<double>, SArray<double> > > > ( Array<double, A_Add<double, A_Mult<double, A_Scalar<double>, SArray<double> >, A_Mult<double, SArray<double>, SArray<double> > > > const& ) x = 1.2 * x + x * y;
  25. main: ... call operator new[](unsigned long)@PLT ... call operator new[](unsigned long)@PLT ... rep stosq ... movaps XMMWORD PTR 80[rsp], xmm0 call Array<double, ... mov rdi, QWORD PTR 32[rsp] test rdi, rdi je .L46 call operator delete[](void*)@PLT .L46: mov rdi, QWORD PTR 16[rsp] test rdi, rdi je .L47 call operator delete[](void*)@PLT
  26. Array<double, SArray<double> >& Array<double, SArray<double> >::operator=<double, A_Add<double, A_Mult<double, A_Scalar<double>, SArray<double> >, A_Mult<double, SArray<double>, SArray<double> > > >(Array<double, A_Add<double, A_Mult<double, A_Scalar<double>, SArray<double> >, A_Mult<double, SArray<double>, SArray<double> > > > const&): push r15 ... .L26: mov r9, QWORD PTR [rdi] mov rsi, QWORD PTR [r12] add rcx, 1 movsd xmm0, QWORD PTR [r9+rdx] mov r9, QWORD PTR 0[rbp] mulsd xmm0, QWORD PTR [rsi+rdx] mov rsi, QWORD PTR [rbx] movsd xmm1, QWORD PTR [r9+rdx] mulsd xmm1, QWORD PTR [rsi] addsd xmm0, xmm1 movsd QWORD PTR [r11], xmm0
  27. benchmarks static void Ideal(benchmark::State& state) { SArray<double> x(1000), y(1000); for(auto _ : state) { for (auto i = 0 ; i < x.size(); ++i) x[i] = 1.2 * x[i] + x[i] * y[i]; } } BENCHMARK(Ideal); static void SuperFast(benchmark::State& state) { Array<double> x(1000), y(1000); for(auto _ : state) { x = 1.2 * x + x * y; } } BENCHMARK(SuperFast);
  28. Benchmark gcc 9.2.0 (-O3) Run on (8 X 3400 MHz CPU s) CPU Caches: L1 Data 32K (x4) L1 Instruction 32K (x4) L2 Unified 256K (x4) L3 Unified 6144K (x1) Load Average: 0.33, 0.35, 0.21 ----------------------------------------------------- Benchmark Time CPU Iterations ----------------------------------------------------- Ideal 255 ns 255 ns 2693296 SuperFast 346 ns 346 ns 2007497
  29. Benchmark gcc 9.2.0 (-O0) Run on (8 X 3400 MHz CPU s) CPU Caches: L1 Data 32K (x4) L1 Instruction 32K (x4) L2 Unified 256K (x4) L3 Unified 6144K (x1) Load Average: 0.33, 0.35, 0.21 ----------------------------------------------------- Benchmark Time CPU Iterations ----------------------------------------------------- Ideal 10123 ns 10119 ns 63845 SuperFast 40989 ns 40971 ns 13674
  30. Итог l Rapidjson быстр, но требует оптимизацию l Шаблоны выражений требуют оптимизацию. l Тестовая библиотека дает - Rapidjson 15-25 МБ/с - Cpp rest sdk 13 МБ/с
  31. Исследуем проект l Часть библиотек с /Z0 (== -O0) l Часть с включенной оптимизацией
  32. Архитектура кода и оптимизация : модуль json.o void readJ(std::string & json) { rapidjson::Document doc; doc.Parse(json.c_str()); } void readJExternal(std::string const& json, rapidjson::Document& doc) { doc.Parse(json.c_str()); }
  33. Архитектура кода и оптимизация: модуль main.o static void JSON_ParseInternal(benchmark::State& state) { std::ifstream in_("canada.json"); auto json = std::string(std::istreambuf_iterator<char>(in_), std::istreambuf_iterator<char>()); for(auto _ : state) { readJ(json); } } BENCHMARK(JSON_ParseInternal);
  34. Архитектура кода и оптимизация: модуль main.o static void JSON_ParseExternal(benchmark::State& state) { std::ifstream in_("canada.json"); auto json = std::string(std::istreambuf_iterator<char>(in_), std::istreambuf_iterator<char>()); rapidjson::Document doc; for(auto _ : state) { readJExternal(json, doc); } } BENCHMARK(JSON_ParseExternal);
  35. Разделение кода и оптимизация: модуль main.o static void JSON_CalcSum(benchmark::State& state) { std::ifstream in_("canada.json"); auto json = std::string(std::istreambuf_iterator<char>(in_), std::istreambuf_iterator<char>()); rapidjson::Document doc; readJExternal(json, doc); for(auto _ : state) { const auto& a = doc["features"][0]["geometry"]["coordinates"][0]; double result = 0.0; for(rapidjson::SizeType i = 0; i < a.Size(); ++i) result += a[i][0].GetDouble() + a[i][1].GetDouble(); } } BENCHMARK(JSON_CalcSum);
  36. Производтельность, разные оптимизации, итераций за цикл Main + O0 Json + O0 Main + O3 Json O3 Main + O3 Json + O0 Main + O0 Json + O3 JSON_ParseInterna l 7 78 7 78 JSON_ParseExterna l 8 75 8 75 JSON_CalcSum 301592 4925519 4925519 301592
  37. План l Дотянуть оптимизацию в разных либах l Перейти на rapidjson l Можем добавить шаблоны выражений, где требуется.
  38. Кирилл Березин Ведущий программист k.berezin@corp.mail.ru
Anúncio