58
Низкоуровневые оптимизации Андрей Аксенов, Sphinx Unigine Open Air 2013

Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

Embed Size (px)

DESCRIPTION

Андрей Аксенов, генеральный директор, Sphinx Technologies (Воронеж)

Citation preview

Page 1: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

Низкоуровневые оптимизации

Андрей Аксенов, Sphinx

Unigine Open Air 2013

Page 2: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 3: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

затрудняюсь– С примерами плохо, без примеров плохо

• Поговорю про низкий уровень

Page 4: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 5: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 6: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

Низкий уровень• Однако “ассемблеров” в 2011 году много– C99, C++03, C++11, Java, C#, LLVM

• Поговорим, выходит, про C/C++– Несмотря, что

auto f = [](){}; // this is valid fucking C++ now

Page 7: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 8: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 9: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 10: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

…а вам с ним жить

BENCH, BENCH, BENCH

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

Page 11: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

• Завтра – Ulrich Drepper, Agner Fog– Звоните, все трое и звоните

Page 12: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 13: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

Что нужно знать про CPU• Что есть регистры, инструкции, кеш кода• Что есть целая арифметика, FPU, MMX, SSE• Что есть mu-ops, pipes, branch prediction,

register renaming, out-of-order execution• Что все эти чудеса стоят разных “денег”• Что amd64 “лучше” i686, когда не “хуже”

Page 14: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

RAM, про цены в целом• Bandwidth. Тупое линейное чтение– 4.6 GB/sec на лаптопе, 12.7 GB/sec на сервере– Ништяк, ништяк?

• Latency. Такие же 100M int32, stride 4096– 195 MB/sec, 2.14 sec/100M, ~49 Mreads/sec– Те. последовательный доступ ~1-2 такта– Те. случайный доступ ~40-60 тактов

Page 15: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 16: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

RAM, L1 кеш• Почему так? Потому что L1/L2 cache• Скачем с шагом N => кеш-миссы =>

тормоза• Шаг 4..64, ~4400..330 MB/sec, ~2x/шаг• Шаг 64..1024, ~330..195 MB/sec• Значит, размер L1 кеш-линии 64 байта :)

Page 17: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

RAM, L2 кеш• А давайте иначе:

вы мне – свою душу,а я вам – домашнийадрес епископаДиомида?

Page 18: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 19: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

RAM, про выравнивание• Лаптоп, 4.6 vs 4.2 G/sec, минус 8.3%• Сервер, 12.7 vs 11.6 G/sec, минус 7.3%• Intel, SPARC, ARM

Page 20: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

RAM, выводы

Page 21: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 22: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013
Page 23: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013
Page 24: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

CPU, про регистры• i286 – ax, bx, cx, dx, si, di, bp, sp• i386 – eax, ebx, ecx, edx, esi, edi, ebp, esp• i686 – eax, ebx, ecx, edx, esi, edi, ebp, esp• amd64 – rax..rsp, r0..r31• fpu (aka i287) – fp(0)..fp(7)• sse – xmm0..xmm7

Page 25: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

• a += b– add eax, ebx ; The Hit

Page 26: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

CPU, VS 2005 vs gcc 4.x• this->a vs a• member write-pressure

int iRes = m_iCounter; // skip nonwhitespace

while ( m_iCounter<m_iLimit && m_sBuffer[m_iCounter] )

m_iCounter++;

• test1 = 701 msec, test2 = 413 msec

Page 27: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 28: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

CPU, про креативное• Pipes, начиная с i586 (Pentium-1)

add eax, ebx

add ecx, edx ; pairs

• Дальше ситуация ухудшилась!!!• Mu-ops, renaming, out-of-order, и всякое

другое увлекательное вуду

Page 29: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

CPU, про ветвления• if ( a!=0 ) { Doit(); } …

cmp eax, 0

jne Label1 ; branch point

call Doit

Label1:

• likely(), unlikely() etc

Page 30: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 31: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

res += ( a==b )? c*d-e: (a+b)*c+d;

• For-if, 0.274 sec, res 1 300 000 000• If-for, 0.184 sec, res 1 300 000 000

Page 32: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

CPU, про FPU, SSEx• Дивный отдельный мир• fpusum, 44042 usec, msvc default• fpusum, 15386 usec, msvc /fp:fast• ssesum, 8453 usec

__m128 res = _mm_set_ps1(0.0f);

while ( p<pmax ) res = _mm_add_ps(res, *p++);

Page 33: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

CPU, про FPU, SSEx• gcc 4.4.3, amd64• fpusum, 11214 usec• ssesum, 3414 usec

Page 34: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

CPU, SSE unroll• ssesum, 8453 usec• ssesum x4, 8240 usec• ssesum x4 + non-naïve, 8056 usec, +5%

res = _mm_add_ps(res, _mm_add_ps(_mm_add_ps(p[0], p[1]),_mm_add_ps(p[2], p[3])));

p += 4;

Page 35: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013
Page 36: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 37: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

cc, флаги сборки• gcc, -march=(i386|i686|core2???)– Умолчания системы могут удивить

• msvc, /arch:(sse|sse2), /fp:(fast|precise|…)– По умолчанию голимый FPU

• msvc, /ZI (aka E&C, edit and continue)– По умолчанию включен… “language”, my ass

Page 38: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

• __cdecl, __stdcall, __fastcall• intrinsics (msvc) aka builtin functions (gcc)– А это не магия! fabs(), strcpy(), итп

• _SECURE_SCL итп отладочные итераторы• msvc 2005 по умолчанию…

__cdecl, NO intrinsics, _SECURE_SCL 1

cc, конвенции вызова

Page 39: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

cc, аллокации• Старые недобрые malloc()/free()– Drop-in замены: nedmalloc, tcmalloc

• Мало “больших” (4-16K+) везде ок• Много мелких аллокаций везде боль– Например, 1 M аллокаций по 16 байт

• Ручные пулы все еще работают!

Page 40: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 41: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

История любви1. ref, wall 11.9

2. magic1, wall 10.1, 1.178x relative, 1.178x total

3. magic123, wall 9.3, 1.086x relative, 1.279x total

4. currpath, wall 8.0, 1.162x relative, 1.487x total

5. noformsort, wall 6.9, 1.159x relative, 1.724x total

6. fastuc, wall 6.3, 1.095x relative, 1.888x total

7. dwordres, wall 5.9, 1.067x relative, 2.016x total

8. ptrres, wall 4.2, 1.404x relative, 2.833x total

9. manrecurse, wall 4.0, 1.050x relative, 2.975x total

Page 42: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

Шаги 1, 2• +17.8% (magic1)– Выкидываем 1-буквенные “слова”– Выкидываем слова “нерусские”

• +8.6% (magic23)– Выкидываем наиболее частые 2/3-буквенные– И ведь всего-то 18 слов

Page 43: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

Шаг 3• Было

std::string currentPath;

DoRecursiveStuff ( currentPath, … );

• Стало +16.2% (currpath)BYTE sPath[128];

DoRecursiveStuff ( sPath, 0, … );

Page 44: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

Шаг 4• Было

vector< pair<string,int> > forms;

Get ( forms );

Sort ( forms );

return forms[0];

Page 45: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

Шаг 4• Стало +15.9% (noformsort)

vector<int> indexes; string currentBest;

Get ( indexes );

for ( int i=0; i<indexes.size(); i++ )

CheckAndUpdate ( currentBest );

return currentBest;

Page 46: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

Шаг 5• Был фарш RmlMakeUpper, FilterSrc• Приводило регистр, заменяло букву Ё• Стала прегенерация BYTE m_UC[256];

while ( *p ) { *p = m_UC[*p]; p++; }• +9.5% (fastuc)

Page 47: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

Шаг 6• Гонялся и возвращался vector<WORD[3]>,

получаемый распаковкой некоего DWORD• Заменил на vector<DWORD>, в нужных

местах добавил распаковку на месте• +9.5% (dwordres)

Page 48: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

Шаг 7/8• Результат писало в vector<DWORD> & Infos• Заменил на DWORD[12], с маркером конца– Максимальная длина результата 6

• +40.4% (ptrres) !!!• vector<DWORD> g_res + g_res.reserve() толк

тоже давали, но меньше (очевидно)

Page 49: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

Шаг 8/8• Развернул inner loop из рекурсии в цикл• Было

int Count = GetChildrenCount(NodeNo); for …

• Стало +5% (manrecurse)int iChild[MAX_DEPTH], iChildMax[MAX_DEPTH];

while ( iLvl>=0 ) while ( iChild[iLvl]<iMax[iLvl] ) …

Page 50: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 51: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

Сводка фокусов• Душим fastpath, прямо в коде– Шаги 1, 2

• Душим RAM/stack pressure аргументов– Шаги 3, 4, 6, 7, 8

• Душим сложность, тупое упрощение влоб!!!– Шаг 5

Page 52: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

memb-pressure, for-if, ptr-walks, movzx, …)

Page 53: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

Сводка третьих фокусов• И еще несколько десятков всяких

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

Page 54: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 55: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013
Page 56: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

Мораль про приемы• Но уж если работать приходится, то по

минимуму• Как именно схалявить – вопрос не всегда

простой• Приходится проявляться изворотливость!

Page 57: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

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

Page 58: Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013

ВОПРОСЫ?!!