4bfb75e4c680e267092115efc7ffc13e.ppt
- Количество слайдов: 58
Низкоуровневые оптимизации Андрей Аксенов, Sphinx Highload++2011
Про что доклад
Про что доклад • • Высокие нагрузки => надо быстро Надо быстро => надо оптимизировать Надо оптимизировать => и так, и эдак Про высокий уровень говорить затрудняюсь – С примерами плохо, без примеров плохо • Поговорю про низкий уровень
Низкий уровень? • Это, конечно, ассемблер – И только ассемблер – Иначе быть не может – Однако!
Низкий уровень
Низкий уровень • Однако “ассемблеров” в 2011 году много – C 99, C++03, C++11, Java, C#, LLVM • Поговорим, выходит, про C/C++ – Несмотря, что auto f = [](){}; // this is valid fucking C++ now
Почему C/C++ • • • Все еще ближе всего к железу Все еще топовые компиляторы Все еще НОД, интегрируется везде Все еще много стороннего софта на Ну и я других “ассемблеров” “не знаю” – “Ну то есть что считать за секс”
Про… лозунги • • • 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 – А давайте его отваром ромашки тогда. Толку не будет, но вреда тоже (c) Гиппократ
256 оттенков серого • Но нас интересуют только 3 • В целом – диск, память, CPU • Сегодня – память, CPU • Завтра – Ulrich Drepper, Agner Fog – Звоните, все трое и звоните
Что нужно знать про RAM • • • Как соотносятся разные цены доступа Что память работает не байтами… Что бывает L 1/L 2 кеш Что кеш вымывается Что (не)выравнивание небесплатно Что бывает (и иногда работает) префетч
Что нужно знать про 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 на лаптопе, 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, про цены на практике • • Читать один поток надо мало… но редко 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 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 кеш • • • Фиксируем шаг 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% • Сервер, 12. 7 vs 11. 6 G/sec, минус 7. 3% • Intel, SPARC, ARM
RAM, выводы
RAM, таки выводы • • Если данных много, лучше линейно Если линейно никак, лучше мало данных! Если можно переложить Тетрис, то нужно Если можно, лучше всякое выровнять – Но держать баланс с локальностью!
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, 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 int i. Res = m_i. Counter; // skip nonwhitespace while ( m_i. Counter
CPU, про кеш кода • Код это тоже данные и тоже память… • Внезапно, функции и конвенции вызова • Внезапно, инлайнить или нет? – Функции, inline, __forceinline, итп мычки – Классы, реализация в декларации – Шаблоны, функторы против коллбэков
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, 0 jne Label 1 call Doit Label 1: … ; branch point • likely(), unlikely() etc
CPU, еще про ветвления • • switch() vs простыня простых if() Ни разу не эквивалентны!!! switch() оптимизируется, if() нет Эффект заметен от 3 (трех) значений
CPU, if-for vs for-if • for ( int i=0; i
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 usec • ssesum, 3414 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 Про флаги сборки (arch, etc) Про конвенции вызова Про волшебные define-ы
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) – А это не магия! fabs(), strcpy(), итп • _SECURE_SCL итп отладочные итераторы • msvc 2005 по умолчанию… __cdecl, NO intrinsics, _SECURE_SCL 1
cc, аллокации • Старые недобрые malloc()/free() – Drop-in замены: nedmalloc, tcmalloc • Мало “больших” (4 -16 K+) везде ок • Много мелких аллокаций везде боль – Например, 1 M аллокаций по 16 байт • Ручные пулы все еще работают!
Боевой пример • • Морфологический словарь, libaot Сторонний код, между прочим! Как именно удалось его взгреть в 3 раза Что делает тот критичный код? – возвращает набор лемм по словоформе – СТАЛИ -> СТАТЬ, СТАЛЬ
История любви 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 -буквенные “слова” – Выкидываем слова “нерусские” • +8. 6% (magic 23) – Выкидываем наиболее частые 2/3 -буквенные – И ведь всего-то 18 слов
Шаг 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
Шаг 4 • Стало +15. 9% (noformsort) vector
Шаг 5 • Был фарш Rml. Make. Upper, Filter. Src • Приводило регистр, заменяло букву Ё • Стала прегенерация BYTE m_UC[256]; while ( *p ) { *p = m_UC[*p]; p++; } • +9. 5% (fastuc)
Шаг 6 • Гонялся и возвращался vector
Шаг 7/8 • Результат писало в vector
Шаг 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 Черновик, замер, чистовик Десяток чистовиков был закоммитан Десяток черновиков сразу выкинут
Сводка фокусов • Душим 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% не лишний Курочка по зернышку…
ВОПРОСЫ? !!


