Эффективное использование x86-совместимых cpu (Алексей...

37
Эффективное использование x86-совместимых CPU Алексей Тутубалин lexa @ lexa.ru CTO @LibRaw LLC Многопоточность, векторные расширения

Upload: ontico

Post on 14-Dec-2014

1.192 views

Category:

Documents


0 download

DESCRIPTION

 

TRANSCRIPT

Page 1: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

Эффективное использование x86-совместимых CPU

Алексей Тутубалин[email protected]

CTO @LibRaw LLC

Многопоточность, векторные расширения

Page 2: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

Производительность 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 операций на такт

Page 3: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

Локальная параллельность• Только в хотспотах– Замена обычных циклов - параллельными– Замена обычных команд – векторными (SIMD:

SSE, AVX)• Сопутствующие изменения– Редизайн/рефакторинг структур данных– Изменения в алгоритмах

Page 4: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

ЛОКАЛЬНАЯ МНОГОПОТОЧНОСТЬ

Page 5: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

Схема работы

• Fork/join на месте длинных циклов

• Накладные расходы– n*1-10µsec на каждый поток

• Методы реализации– Полуавтоматически: OpenMP– Вручную: threading-библиотеки

Page 6: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

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];

Page 7: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

OpenMP

• Чужеродность для С++– Нет поддержки exceptions

• Не все компиляторы поддерживают• Бывают ошибки в рантайме (или

компиляторе)• Альтернатива: Cilk Plus (Intel C++, gcc 4.8

branch)

Page 8: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

Параллельные библиотекиIntel TBB: суммирование вектора

Page 9: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

Итого

1. Это работает !2. Эффективность:– Обычно 75-85% (3-3.5x speedup на 4-core)– >90% для «гигабайт» данных

3. Дополнительный код: – 1-20 строк на каждую точку оптимизации

Page 10: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

SIMD-КОМАНДЫ (SSE, AVX)

Page 11: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

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];

Page 12: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

X86: много сортов SIMD

• Под что код делать будем?– 6 версий SSE– 2 версии AVX

• Чем новее версия– Тем больше вкусных команд– И тем уже поддержка

Page 13: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

SSE/AVX: что умеет

Что есть:• Команды– Load/store– Арифметика– Сравнения, min, max– Конверсия типов– Битовые операции– Перестановка

элементов вектора– Dot Product, CRC32,

AES, STRCMP

Что есть:• 16 (8) регистров• Типы данных– Целые: 8, 16, 32, 64 бит– Плавающие: 32, 64

Чего нет:• Flow control• Scatter/gather

Page 14: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

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 запись

Page 15: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

SSE: выравнивание

• До Intel Core-i7:– aligned (16 байт) load/store – сильно быстрее

(~4 раза для Core2Duo)• Начиная с Core-i7:– Почти нет разницы (<10%)

Page 16: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

С/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)

Page 17: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

Откуда берется SSE/AVX-код?

1. Генерирует C/C++ компилятор (автоматическая векторизация)

2. Пишется руками– макросы компилятора (compiler intrinsics)– ассемблер

Page 18: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

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)• Может переупорядочить

команды

Page 19: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

Ручной код: compiler intrinsics

C: 277 Mpix/sec (8.8 Gb/sec)компилятор частично векторизовал

SSE/AVX: 584 Mpix/sec (18.6 Gb/s)

Page 20: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

SIMD: Итого1. Это не больно!2. Выигрыш в 2-4 раза - запросто3. Трудный старт4. Где брать информацию:– Intel Software Developers Manual, Vol 2A & 2B – MSDN: руководство по Visual Studio, много

мелких примеров по _mm-макросам– Собирать по крупицам…

Page 21: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

ПАРАЛЛЕЛЬНЫЕ/SIMD ЯЗЫКИ

Page 22: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

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

Page 23: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

ISPC: пример и синтаксис

Сложение векторов Расширение C:• uniform – разделяемая копия

переменной на все параллельные/SIMD потокиvarying – приватная копия

• foreach/foreach_tiled – SIMD-цикл (тело цикла векторизуется) запускается

• launch - запуск задачи• Встроенные переменные:

programIndex – номер SIMD-laneprogramCount – ширина SIMDtaskIndex – номер задачи

• Далее – см. документацию

Page 24: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

ISPC: многопоточность

Page 25: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

ISPC: практика

Page 26: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

OpenCL• Стандарт для «гетерогенных вычислений»• Платформы: CPU/GPU• Особенности– Компиляция на лету– Двойная буферизация– Отдельный внешний рантайм– Громоздкий API

• Применение– Для больших объемов вычислений– Если планируется переход на GPU-расчеты

Page 27: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

Позитив

• Ускорение в 10-15 раз – достижимо• Вероятно, потребуется рефакторинг

Но только один раз:– Параллельный код неплохо масштабируется– Рефакторинг для CPU => годится для GPU

• Но лучше – сразу писать эффективно, помня о грядущей паралельности

Page 28: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

СПАСИБО ЗА ВНИМАНИЕ! ВОПРОСЫ?

Презентация (со ссылками) доступна:www.slideshare.net/AlexTutubalin

Я иногда пишу на эти темы:blog.lexa.ru/tags/sse

Page 29: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

ДОПОЛНИТЕЛЬНЫЕ МАТЕРИАЛЫ

Page 30: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

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 – приятное

исключение из общего правила

Page 31: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

BW/Latency: решения• Нужно не зависеть от Latency:– Последовательный код– Нет зависимости по данным– Последовательный доступ к памяти (и диску)– Prefetch в кэши

• А любая случайность – зло:– Обращение по указателю– Синхронизация (!)– Не предсказанный условный переход– Случайный disk seek

Page 32: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

Векторизация С/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

Page 33: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

Векторизация в 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

Page 34: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

Модификация кода для успешной векторизации

Не векторизуется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];}

Page 35: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

ISPC: ускорение(для поставляемых примеров)

Page 36: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

OpenCL: корни в GPGPU

• Много отдельных потоков (Work Items)

• Объединенных в блоки с быстрой общей памятью (Work Groups)

• Блоков много, порядок исполнения не определен

• Общая глобальная (медленная) память

Page 37: Эффективное использование x86-совместимых CPU (Алексей Тутубалин)

OpenCL: сложение векторов