Скачать презентацию Низкоуровневые оптимизации Андрей Аксенов Sphinx Highload 2011 Про Скачать презентацию Низкоуровневые оптимизации Андрей Аксенов Sphinx Highload 2011 Про

4bfb75e4c680e267092115efc7ffc13e.ppt

  • Количество слайдов: 58

Низкоуровневые оптимизации Андрей Аксенов, Sphinx Highload++2011 Низкоуровневые оптимизации Андрей Аксенов, Sphinx Highload++2011

Про что доклад Про что доклад

Про что доклад • • Высокие нагрузки => надо быстро Надо быстро => надо Про что доклад • • Высокие нагрузки => надо быстро Надо быстро => надо оптимизировать Надо оптимизировать => и так, и эдак Про высокий уровень говорить затрудняюсь – С примерами плохо, без примеров плохо • Поговорю про низкий уровень

Низкий уровень? • Это, конечно, ассемблер – И только ассемблер – Иначе быть не Низкий уровень? • Это, конечно, ассемблер – И только ассемблер – Иначе быть не может – Однако!

Низкий уровень Низкий уровень

Низкий уровень • Однако “ассемблеров” в 2011 году много – C 99, C++03, C++11, Низкий уровень • Однако “ассемблеров” в 2011 году много – C 99, C++03, C++11, Java, C#, LLVM • Поговорим, выходит, про C/C++ – Несмотря, что auto f = [](){}; // this is valid fucking C++ now

Почему C/C++ • • • Все еще ближе всего к железу Все еще топовые Почему C/C++ • • • Все еще ближе всего к железу Все еще топовые компиляторы Все еще НОД, интегрируется везде Все еще много стороннего софта на Ну и я других “ассемблеров” “не знаю” – “Ну то есть что считать за секс”

Про… лозунги • • • RT 3 D == Batch, batch (c) ATI/Nvidia VLDB Про… лозунги • • • RT 3 D == Batch, batch (c) ATI/Nvidia VLDB == Shard, shard (c) everyone Multicore == Thread, thread (c) Intel TDD == Test, test (c) K. Beck? Agile == Sprint, sprint (c) M. Fowler? Optimizations == ? ? ?

Нам лозунг строить… Нам лозунг строить…

…а вам с ним жить BENCH, BENCH – А давайте его отваром ромашки тогда. …а вам с ним жить BENCH, BENCH – А давайте его отваром ромашки тогда. Толку не будет, но вреда тоже (c) Гиппократ

256 оттенков серого • Но нас интересуют только 3 • В целом – диск, 256 оттенков серого • Но нас интересуют только 3 • В целом – диск, память, CPU • Сегодня – память, CPU • Завтра – Ulrich Drepper, Agner Fog – Звоните, все трое и звоните

Что нужно знать про RAM • • • Как соотносятся разные цены доступа Что Что нужно знать про RAM • • • Как соотносятся разные цены доступа Что память работает не байтами… Что бывает L 1/L 2 кеш Что кеш вымывается Что (не)выравнивание небесплатно Что бывает (и иногда работает) префетч

Что нужно знать про CPU • Что есть регистры, инструкции, кеш кода • Что Что нужно знать про CPU • Что есть регистры, инструкции, кеш кода • Что есть целая арифметика, FPU, MMX, SSE • Что есть mu-ops, pipes, branch prediction, register renaming, out-of-order execution • Что все эти чудеса стоят разных “денег” • Что amd 64 “лучше” i 686, когда не “хуже”

RAM, про цены в целом • Bandwidth. Тупое линейное чтение – 4. 6 GB/sec RAM, про цены в целом • Bandwidth. Тупое линейное чтение – 4. 6 GB/sec на лаптопе, 12. 7 GB/sec на сервере – Ништяк, ништяк? • Latency. Такие же 100 M int 32, stride 4096 – 195 MB/sec, 2. 14 sec/100 M, ~49 Mreads/sec – Те. последовательный доступ ~1 -2 такта – Те. случайный доступ ~40 -60 тактов

RAM, про цены на практике • • Читать один поток надо мало… но редко RAM, про цены на практике • • Читать один поток надо мало… но редко 100 M x { *c++ = (*a++) + (*b++); } Линейно 1111 MB/s (вместо 4400+) С шагом 4 KB выходит 36 MB/s (вместо 195) – Падение в 5. 4 раза – Жалких 9 Mop/s, 240 тактов на одну (omfg)

RAM, L 1 кеш • • • Почему так? Потому что L 1/L 2 RAM, L 1 кеш • • • Почему так? Потому что L 1/L 2 cache Скачем с шагом N => кеш-миссы => тормоза Шаг 4. . 64, ~4400. . 330 MB/sec, ~2 x/шаг Шаг 64. . 1024, ~330. . 195 MB/sec Значит, размер L 1 кеш-линии 64 байта : )

RAM, L 2 кеш • А давайте иначе: вы мне – свою душу, а RAM, L 2 кеш • А давайте иначе: вы мне – свою душу, а я вам – домашний адрес епископа Диомида?

RAM, L 2 кеш • • • Фиксируем шаг 1024, уменьшаем данные 100 M, RAM, L 2 кеш • • • Фиксируем шаг 1024, уменьшаем данные 100 M, …, 4 M, 3 M, 2. 3 M == 195 MB/sec 2 M == 648 MB/sec 1 M == 1688 MB/sec 512 K == 1724 MB/sec Все сходится, на лаптопе 2 MB L 2 cache

RAM, про выравнивание • Лаптоп, 4. 6 vs 4. 2 G/sec, минус 8. 3% RAM, про выравнивание • Лаптоп, 4. 6 vs 4. 2 G/sec, минус 8. 3% • Сервер, 12. 7 vs 11. 6 G/sec, минус 7. 3% • Intel, SPARC, ARM

RAM, выводы RAM, выводы

RAM, таки выводы • • Если данных много, лучше линейно Если линейно никак, лучше RAM, таки выводы • • Если данных много, лучше линейно Если линейно никак, лучше мало данных! Если можно переложить Тетрис, то нужно Если можно, лучше всякое выровнять – Но держать баланс с локальностью!

CPU, про регистры • • • i 286 – ax, bx, cx, dx, si, CPU, про регистры • • • i 286 – ax, bx, cx, dx, si, di, bp, sp i 386 – eax, ebx, ecx, edx, esi, edi, ebp, esp i 686 – eax, ebx, ecx, edx, esi, edi, ebp, esp amd 64 – rax. . rsp, r 0. . r 31 fpu (aka i 287) – fp(0). . fp(7) sse – xmm 0. . xmm 7

CPU, про инструкции • this->a += b – mov eax, [esi+12] – add eax, CPU, про инструкции • this->a += b – mov eax, [esi+12] – add eax, ebx – mov [esi+12], eax ; The Load ; The Hit ; The Store • a += b – add eax, ebx ; The Hit

CPU, VS 2005 vs gcc 4. x • this->a vs a • member write-pressure CPU, VS 2005 vs gcc 4. x • this->a vs a • member write-pressure int i. Res = m_i. Counter; // skip nonwhitespace while ( m_i. Counter

CPU, про кеш кода • Код это тоже данные и тоже память… • Внезапно, CPU, про кеш кода • Код это тоже данные и тоже память… • Внезапно, функции и конвенции вызова • Внезапно, инлайнить или нет? – Функции, inline, __forceinline, итп мычки – Классы, реализация в декларации – Шаблоны, функторы против коллбэков

CPU, про креативное • Pipes, начиная с i 586 (Pentium-1) add eax, ebx add CPU, про креативное • Pipes, начиная с i 586 (Pentium-1) add eax, ebx add ecx, edx ; pairs • Дальше ситуация ухудшилась!!! • Mu-ops, renaming, out-of-order, и всякое другое увлекательное вуду

CPU, про ветвления • if ( a!=0 ) { Doit(); } … cmp eax, CPU, про ветвления • if ( a!=0 ) { Doit(); } … cmp eax, 0 jne Label 1 call Doit Label 1: … ; branch point • likely(), unlikely() etc

CPU, еще про ветвления • • switch() vs простыня простых if() Ни разу не CPU, еще про ветвления • • switch() vs простыня простых if() Ни разу не эквивалентны!!! switch() оптимизируется, if() нет Эффект заметен от 3 (трех) значений

CPU, if-for vs for-if • for ( int i=0; i<NITER; i++ ) res += CPU, if-for vs for-if • for ( int i=0; i

CPU, про FPU, SSEx • • Дивный отдельный мир fpusum, 44042 usec, msvc default CPU, про FPU, SSEx • • Дивный отдельный мир fpusum, 44042 usec, msvc default fpusum, 15386 usec, msvc /fp: fast ssesum, 8453 usec __m 128 res = _mm_set_ps 1(0. 0 f); while ( p

CPU, про FPU, SSEx • gcc 4. 4. 3, amd 64 • fpusum, 11214 CPU, про FPU, SSEx • gcc 4. 4. 3, amd 64 • fpusum, 11214 usec • ssesum, 3414 usec

CPU, SSE unroll • ssesum, 8453 usec • ssesum x 4, 8240 usec • CPU, SSE unroll • ssesum, 8453 usec • ssesum x 4, 8240 usec • ssesum x 4 + non-naïve, 8056 usec, +5% res = _mm_add_ps(res, _mm_add_ps(p[0], p[1]), _mm_add_ps(p[2], p[3]))); p += 4;

Что нужно знать про cc/libc • • Компилятор == gcc, MSVC, (припадочно) ICC Про Что нужно знать про cc/libc • • Компилятор == gcc, MSVC, (припадочно) ICC Про флаги сборки (arch, etc) Про конвенции вызова Про волшебные define-ы

cc, флаги сборки • gcc, -march=(i 386|i 686|core 2? ? ? ) – Умолчания cc, флаги сборки • gcc, -march=(i 386|i 686|core 2? ? ? ) – Умолчания системы могут удивить • msvc, /arch: (sse|sse 2), /fp: (fast|precise|…) – По умолчанию голимый FPU • msvc, /ZI (aka E&C, edit and continue) – По умолчанию включен… “language”, my ass

cc, конвенции вызова • __cdecl, __stdcall, __fastcall • intrinsics (msvc) aka builtin functions (gcc) cc, конвенции вызова • __cdecl, __stdcall, __fastcall • intrinsics (msvc) aka builtin functions (gcc) – А это не магия! fabs(), strcpy(), итп • _SECURE_SCL итп отладочные итераторы • msvc 2005 по умолчанию… __cdecl, NO intrinsics, _SECURE_SCL 1

cc, аллокации • Старые недобрые malloc()/free() – Drop-in замены: nedmalloc, tcmalloc • Мало “больших” cc, аллокации • Старые недобрые malloc()/free() – Drop-in замены: nedmalloc, tcmalloc • Мало “больших” (4 -16 K+) везде ок • Много мелких аллокаций везде боль – Например, 1 M аллокаций по 16 байт • Ручные пулы все еще работают!

Боевой пример • • Морфологический словарь, libaot Сторонний код, между прочим! Как именно удалось Боевой пример • • Морфологический словарь, libaot Сторонний код, между прочим! Как именно удалось его взгреть в 3 раза Что делает тот критичный код? – возвращает набор лемм по словоформе – СТАЛИ -> СТАТЬ, СТАЛЬ

История любви 1. 2. 3. 4. 5. 6. 7. 8. 9. ref, magic 123, История любви 1. 2. 3. 4. 5. 6. 7. 8. 9. ref, magic 123, currpath, noformsort, fastuc, dwordres, ptrres, manrecurse, wall 11. 9 wall 10. 1, wall 9. 3, wall 8. 0, wall 6. 9, wall 6. 3, wall 5. 9, wall 4. 2, wall 4. 0, 1. 178 x relative, 1. 086 x relative, 1. 162 x relative, 1. 159 x relative, 1. 095 x relative, 1. 067 x relative, 1. 404 x relative, 1. 050 x relative, 1. 178 x total 1. 279 x total 1. 487 x total 1. 724 x total 1. 888 x total 2. 016 x total 2. 833 x total 2. 975 x total

Шаги 1, 2 • +17. 8% (magic 1) – Выкидываем 1 -буквенные “слова” – Шаги 1, 2 • +17. 8% (magic 1) – Выкидываем 1 -буквенные “слова” – Выкидываем слова “нерусские” • +8. 6% (magic 23) – Выкидываем наиболее частые 2/3 -буквенные – И ведь всего-то 18 слов

Шаг 3 • Было std: : string current. Path; Do. Recursive. Stuff ( current. Шаг 3 • Было std: : string current. Path; Do. Recursive. Stuff ( current. Path, … ); • Стало +16. 2% (currpath) BYTE s. Path[128]; Do. Recursive. Stuff ( s. Path, 0, … );

Шаг 4 • Было vector< pair<string, int> > forms; Get ( forms ); Sort Шаг 4 • Было vector< pair > forms; Get ( forms ); Sort ( forms ); return forms[0];

Шаг 4 • Стало +15. 9% (noformsort) vector<int> indexes; string current. Best; Get ( Шаг 4 • Стало +15. 9% (noformsort) vector indexes; string current. Best; Get ( indexes ); for ( int i=0; i

Шаг 5 • Был фарш Rml. Make. Upper, Filter. Src • Приводило регистр, заменяло Шаг 5 • Был фарш Rml. Make. Upper, Filter. Src • Приводило регистр, заменяло букву Ё • Стала прегенерация BYTE m_UC[256]; while ( *p ) { *p = m_UC[*p]; p++; } • +9. 5% (fastuc)

Шаг 6 • Гонялся и возвращался vector<WORD[3]>, получаемый распаковкой некоего DWORD • Заменил на Шаг 6 • Гонялся и возвращался vector, получаемый распаковкой некоего DWORD • Заменил на vector, в нужных местах добавил распаковку на месте • +9. 5% (dwordres)

Шаг 7/8 • Результат писало в vector<DWORD> & Infos • Заменил на DWORD[12], с Шаг 7/8 • Результат писало в vector & Infos • Заменил на DWORD[12], с маркером конца – Максимальная длина результата 6 • +40. 4% (ptrres) !!! • vector g_res + g_res. reserve() толк тоже давали, но меньше (очевидно)

Шаг 8/8 • Развернул inner loop из рекурсии в цикл • Было int Count Шаг 8/8 • Развернул inner loop из рекурсии в цикл • Было int Count = Get. Children. Count(Node. No); for … • Стало +5% (manrecurse) int i. Child[MAX_DEPTH], i. Child. Max[MAX_DEPTH]; while ( i. Lvl>=0 ) while ( i. Child[i. Lvl]

Неявный шаг номер 0 • • • BENCH, BENCH Как следствие profile, profile Черновик, Неявный шаг номер 0 • • • BENCH, BENCH Как следствие profile, profile Черновик, замер, чистовик Десяток чистовиков был закоммитан Десяток черновиков сразу выкинут

Сводка фокусов • Душим fastpath, прямо в коде – Шаги 1, 2 • Душим Сводка фокусов • Душим fastpath, прямо в коде – Шаги 1, 2 • Душим RAM/stack pressure аргументов – Шаги 3, 4, 6, 7, 8 • Душим сложность, тупое упрощение влоб!!! – Шаг 5

Сводка других фокусов • • • Душим лишнюю индирекцию Душим аллокации, пулим всякое Душим Сводка других фокусов • • • Душим лишнюю индирекцию Душим аллокации, пулим всякое Душим использование RAM (локальность!) Душим “плохие” общие структуры Душим arch-specific фокусы (switch, sse, memb-pressure, for-if, ptr-walks, movzx, …)

Сводка третьих фокусов • И еще несколько десятков всяких удивительных мелочей • Из которых Сводка третьих фокусов • И еще несколько десятков всяких удивительных мелочей • Из которых лично я…

Мораль про приемы • Приемов много, общее правило одно • Идеально вообще не работать!!! Мораль про приемы • Приемов много, общее правило одно • Идеально вообще не работать!!!

Мораль про приемы • Но уж если работать приходится, то по минимуму • Как Мораль про приемы • Но уж если работать приходится, то по минимуму • Как именно схалявить – вопрос не всегда простой • Приходится проявляться изворотливость!

Мораль процесс • • Запрофайлил, забенчмаркал, попробовал Намылил, прополоскал, отжал, повторил Каждый лишний 1% Мораль процесс • • Запрофайлил, забенчмаркал, попробовал Намылил, прополоскал, отжал, повторил Каждый лишний 1% не лишний Курочка по зернышку…

ВОПРОСЫ? !! ВОПРОСЫ? !!