Эффективное использование x86-совместимых cpu (Алексей...
DESCRIPTION
TRANSCRIPT
Эффективное использование x86-совместимых CPU
Алексей Тутубалин[email protected]
CTO @LibRaw LLC
Многопоточность, векторные расширения
Производительность CPU 2005-2012:
Практически постоянна?Производительность CPU = частота * (число инструкций на такт)• Тактовая частота
2005 – 2.5-3.6 Ghz2012 – 1.8-3.5 Ghz
• Инструкции/тактпосле Intel Core2 => роста очень медленный
Выросла в 32-64 раза?• Многоядерность
– С 2005 по 2012: рост в 4-8 раз
• SSE, AVX– 2005: 2 операции на такт– 2012: 16 операций на такт
Локальная параллельность• Только в хотспотах– Замена обычных циклов - параллельными– Замена обычных команд – векторными (SIMD:
SSE, AVX)• Сопутствующие изменения– Редизайн/рефакторинг структур данных– Изменения в алгоритмах
ЛОКАЛЬНАЯ МНОГОПОТОЧНОСТЬ
Схема работы
• Fork/join на месте длинных циклов
• Накладные расходы– n*1-10µsec на каждый поток
• Методы реализации– Полуавтоматически: OpenMP– Вручную: threading-библиотеки
OpenMP• Простой синтаксис: #pragma в
последовательном коде#pragma omp parallel forfor(i=0;i<N;i++)
a[i] = b[i]+c[i];
• Возможности: reduction, atomics, critical sections, режим доступа к переменным:
sum=0.0;#pragma omp parallel for reduction(+:sum)for(i=0;i<N;i++)
sum+=vector[i];
OpenMP
• Чужеродность для С++– Нет поддержки exceptions
• Не все компиляторы поддерживают• Бывают ошибки в рантайме (или
компиляторе)• Альтернатива: Cilk Plus (Intel C++, gcc 4.8
branch)
Параллельные библиотекиIntel TBB: суммирование вектора
Итого
1. Это работает !2. Эффективность:– Обычно 75-85% (3-3.5x speedup на 4-core)– >90% для «гигабайт» данных
3. Дополнительный код: – 1-20 строк на каждую точку оптимизации
SIMD-КОМАНДЫ (SSE, AVX)
SSE/AVX: что это такое?
• SSE: одновременные операциинад 128-битными векторами
addps xmm0,xmm1 означает: for(i=0;i<4;i++)
xmm0[i]+=xmm1[i];
• AVX: вектор 128 или 256 бит, 3-адресность:vaddps ymm0,ymm1,ymm2означает: for(i=0;i<8;i++)
ymm0[i]=ymm1[i]+ymm2[i];
X86: много сортов SIMD
• Под что код делать будем?– 6 версий SSE– 2 версии AVX
• Чем новее версия– Тем больше вкусных команд– И тем уже поддержка
SSE/AVX: что умеет
Что есть:• Команды– Load/store– Арифметика– Сравнения, min, max– Конверсия типов– Битовые операции– Перестановка
элементов вектора– Dot Product, CRC32,
AES, STRCMP
Что есть:• 16 (8) регистров• Типы данных– Целые: 8, 16, 32, 64 бит– Плавающие: 32, 64
Чего нет:• Flow control• Scatter/gather
SSE/AVX: упаковка данныхСтандартный ООП-вариантArray Of Structures (AoS)class foo { float x,y,z;void calc_z(){z=A*x+B*y;}};vector<foo> fv(..);for(…..) fv[i].calc_z();
• for(..)calc_z() – плохо векторизуется:– 4-8 чтений– 3 оп. арифметики– 4 записи
Structure of Arrays (SoA)class foo_vector { vector<float> x,y,z;};foo_vector::calc_z()
for(..) z[i]=A*x[i]+B*y[i];
calc_z() – хорошо векторизуется:– 2 чтения– 3 оп. арифметики– 1 запись
SSE: выравнивание
• До Intel Core-i7:– aligned (16 байт) load/store – сильно быстрее
(~4 раза для Core2Duo)• Начиная с Core-i7:– Почти нет разницы (<10%)
С/C++: выравнивание• Переменные и поля в структурах:Gcc: int bar[64] __attribute_((aligned(16)));MSVC:__declspec(align(16)) float baz[32]; • Простые аллокации:
– MSVC: _aligned_malloc(size, align)/_aligned_free()– Gcc: _mm_malloc(size, align)/_mm_free()
• STL: выравнивающий аллокатор:std::vector<T, AlignmentAllocator<T, 16> > foo;
Все элементы вектора выровнены на 16: class __attribute__ ((aligned (16))) Foo { __attribute__ ((aligned (16))) float bar[4];}; std::vector<Foo, AlignmentAllocator<Foo, 16> > baz;
У последнего примера – проблемы с Microsoft STL (починено в VS2012)
Откуда берется SSE/AVX-код?
1. Генерирует C/C++ компилятор (автоматическая векторизация)
2. Пишется руками– макросы компилятора (compiler intrinsics)– ассемблер
Compiler intrinsics: нудную работу должен делать компилятор
Стандартные типы данных
__m128 => SSE-регистр (float[4] или int16[8] или еще 6 вариантов)
__m128d => double[2]
Типы AVX-256:__m256 => float[8]__m256d => double[4]
Макросы для команд__m128 _mm_add_ps(__m128,__m128)
=> addps (или vaddps)
Компилятор:• Распределяет регистры• Подставляет адреса C-
переменных• Генерирует код под целевую
архитектуру (SSE, AVX)• Может переупорядочить
команды
Ручной код: compiler intrinsics
C: 277 Mpix/sec (8.8 Gb/sec)компилятор частично векторизовал
SSE/AVX: 584 Mpix/sec (18.6 Gb/s)
SIMD: Итого1. Это не больно!2. Выигрыш в 2-4 раза - запросто3. Трудный старт4. Где брать информацию:– Intel Software Developers Manual, Vol 2A & 2B – MSDN: руководство по Visual Studio, много
мелких примеров по _mm-макросам– Собирать по крупицам…
ПАРАЛЛЕЛЬНЫЕ/SIMD ЯЗЫКИ
Intel SPMD Program Compiler (ISPC)• SPMD – Single Program, Multiple Data• Метафора: «пишем программу для одного SIMD-
элемента, компилятор расширит на весь вектор»• x86: SSE2, SSE4, AVX, AVX2, Xeon Phi• Поддерживается Intel: BSD-license, opensource,
http://ispc.github.com
ISPC: пример и синтаксис
Сложение векторов Расширение C:• uniform – разделяемая копия
переменной на все параллельные/SIMD потокиvarying – приватная копия
• foreach/foreach_tiled – SIMD-цикл (тело цикла векторизуется) запускается
• launch - запуск задачи• Встроенные переменные:
programIndex – номер SIMD-laneprogramCount – ширина SIMDtaskIndex – номер задачи
• Далее – см. документацию
ISPC: многопоточность
ISPC: практика
OpenCL• Стандарт для «гетерогенных вычислений»• Платформы: CPU/GPU• Особенности– Компиляция на лету– Двойная буферизация– Отдельный внешний рантайм– Громоздкий API
• Применение– Для больших объемов вычислений– Если планируется переход на GPU-расчеты
Позитив
• Ускорение в 10-15 раз – достижимо• Вероятно, потребуется рефакторинг
Но только один раз:– Параллельный код неплохо масштабируется– Рефакторинг для CPU => годится для GPU
• Но лучше – сразу писать эффективно, помня о грядущей паралельности
СПАСИБО ЗА ВНИМАНИЕ! ВОПРОСЫ?
Презентация (со ссылками) доступна:www.slideshare.net/AlexTutubalin
Я иногда пишу на эти темы:blog.lexa.ru/tags/sse
ДОПОЛНИТЕЛЬНЫЕ МАТЕРИАЛЫ
Bandwidth vs LatencyBandwidth Latency
RAM,1980 13 MB/s 225 ns
RAM, 2000 1600 MB/s(~125x)
52 ns(~4.5x)
RAM, 2012 12800 MB/s(~1000x)
35ns(~6.5x)
HDD, 1980 0.6 MB/s 48 ms
HDD, 2000 86 MB/s(~150x)
5.7 ms(~8.5x)
HDD,2012 300 MB/s(~500x)
~5 ms(~10x)
10 Mbit Ethernet
1 MB/s 3000 µsec
56Gbit Infiniband
~5000 MB/s(5000x)
0.7 µsec(~4300x)
• Случайный доступ - медленный– «Память – это новый диск»– «Диск – это новая лента»
• У CPU – то же самое!!• Кэши – немного спасают• LAN – приятное
исключение из общего правила
BW/Latency: решения• Нужно не зависеть от Latency:– Последовательный код– Нет зависимости по данным– Последовательный доступ к памяти (и диску)– Prefetch в кэши
• А любая случайность – зло:– Обращение по указателю– Синхронизация (!)– Не предсказанный условный переход– Случайный disk seek
Векторизация С/C++: алиасингСложение двух векторов:void sum_vec(float *s1,float *s2, float *d, int N){
for(int i=0;i<N;i++)d[i] = s1[i]+ s2[i];
}Вызов:float A[SZ]; sum_vec(A,A+1,A+2,SZ-2);Решение: restrict (C99), __restrict__(gcc C++), __restrict (MSVC):void sum_vec(float * __restrict__ s1,float* __restrict__ s2, float* __restrict__ d….Но:• Алиасинг бывает и на this• В библиотечных классах – может не быть указан restrict
Векторизация в C++ компиляторах
• «Обычный код» (после базовых правок выравнивания и алиасинга):– Личный опыт: ускорения «в разы» компилятор не дает, нужно
вручную.– Чужой опыт: «An Evaluation of Vectorizing Compilers» (2011)
http://polaris.cs.uiuc.edu/~garzaran/doc/pact11.pdfДля кода Media Bench II получили: • Intel C++/GCC: среднее ускорение 1.2 раза.• Ручной ассемблерный код: ускорение 2.7 раза.
• Нужны серьезные правки кода, см. напримерProgram Optimization Trough Loop Vectorizationhttps://wiki.engr.illinois.edu/download/attachments/114688007/9-Vectorization.pdf
Модификация кода для успешной векторизации
Не векторизуетсяfor(i=0;i<LEN;i++){ sum = 0.0; for(j=0;j<LEN;j++) sum+=A[j][i]; B[i] = sum;}
Векторизуетсяfor(i=0;i<LEN;i++){ sum[i] = 0.0; for(j=0;j<LEN;j++) sum[i]+=A[j][i]; B[i] = sum[i];}
ISPC: ускорение(для поставляемых примеров)
OpenCL: корни в GPGPU
• Много отдельных потоков (Work Items)
• Объединенных в блоки с быстрой общей памятью (Work Groups)
• Блоков много, порядок исполнения не определен
• Общая глобальная (медленная) память
OpenCL: сложение векторов