446f7e40885c51c280f4a11a614b81dd.ppt
- Количество слайдов: 38
Векторизация
Тенденция в развитии микропроцессоров: Последовательное выполнение инструкций Параллельное выполнение инструкций • Конвейеризация • Суперскалярность • Векторные операции • Многоядерные и многопроцесорные решения Оптимизирующий компилятор – инструмент, транслирующий исходный код программы в исполняемый модуль, а так же инструмент, оптимизирующий исходный код для получения лучшей производительности. Распараллеливание – трансформация последовательно исполняемой программы в программу, в которой наборы инструкций выполняются одновременно, и сохраняется результат работы.
Одна из технологий распараллеливания программы – векторизация циклов C[1] C[2] C[3] C[4] = = A[1] A[2] A[3] A[4] A[1]+B[1] A[2]+B[2] A[3]+B[3] A[4]+B[4] Векторизация A[1] A[2] A[3] A[4] + B[1] B[2] B[3] B[4] = C[1] C[2] C[3] C[4] B[1] B[2] B[3] B[4]
Типичная векторная инструкция представляет собой операцию над двумя векторами в памяти или в регистрах фиксированной длины. Векторные регистры могут быть загружены из памяти за одну операцию или по частям. Векторизация - это компиляторная оптимизация, которая заменяет скалярный код на векторный. Эта оптимизация «упаковывает» данные в вектора и заменяет скалярные операции на операции с такими векторами (пакетами). A(1: n: k) – секция массива в Фортране. for(i=0; i … Sn: lhsn[i: i+vl-1: 1] = rhsn[i: i+vl+1: 1]; }
MMX, SSE Векторные инструкции и векторизация Первоначально была предложена технология MMX (Multimedia Extensions) – набор инструкций, выполняющих характерные для процессов кодирования/декодирования потоковых аудио/видео данных действия. MMM 0 -MMM 7 64 -битные регистры для работы с целыми числами концепция пакетов, каждый из этих регистров мог хранить 2 – 32 -битных целых числа 4 - 16 -битных 8 - 8 -битных 47 инструкций, которые деляться на несколько груп: перемещение данных арифметические сравнения конвертации логические распаковки сдвига пустые инструкции получения состояния
SSE (Streaming SIMD Extensions, потоковое SIMDрасширение процессора) - это набор инструкций, позволяющий работать с множеством данных – SIMD(Single Instruction, Multiple Data, Одна инструкция — множество данных). • • поддерживает 8 128 -битных регистров (xmm 0 до xmm 7); производит операции со скалярными и упакованными типами данны набор инструкций, который х. Технология EMM 64 T добавляла к этому набору еще 8 128 -битных регистра (xmm 8 до xmm 15). SSE 2, SSE 3, SSE 4 – последующие расширения этой идеи. SSE 2 добавил тип упакованных данных с плавающей точкой двойной точности. AVX - новое расширение системы команд (Advanced vector extensions). AVX предоставляет различные улучшения, новые инструкции и новую схему кодирования машинных кодов. Размер векторных регистров SIMD увеличивается с 128 до 256 бит. Регистры YMM 0 -YMM 15. Существующие 128 -битные инструкции используют младшую половину YMM регистров.
Данные различных типов могут быть упакованы в векторные регистры следующим образом: Упакованный тип данных signed bytes unsigned bytes signed words unsigned words signed doublewords unsigned doublewords signed quadwords unsigned quadwords single-precision fps double-precision fps Длина вектора 16 16 8 8 4 4 2 2 4 2 Число битов на элемент 8 8 16 16 32 32 64 64 32 64 Область значений типа -2**7 до 2**7 -1 0 до 2**8 -1 -2**15 до 2**15 -1 0 до 2**16 -2**31 до 2**31 -1 0 до 2**32 -1 -2**63 до 2*63 -1 0 до 2**64 -1 2**-126 до 2**127 2**-1022 до 2**1023 Выбор подходящего для вычислений типа данных может существенно сказаться на производительности приложения.
Optimization with Switches SIMD – SSE, SSE 2, SSE 3, SSE 4. 2 Support 2 x doubles 4 x floats 1 x dqword SSE 4. 2 SSE 3 SSE 2 16 x bytes SSE 8 x words MMX* 4 x dwords 2 x qwords * MMX actually used the x 87 Floating Point Registers - SSE, SSE 2, and SSE 3 use the new SSE registers 8
Группы инструкций: Инструкции перемещения данных: (data movement instructions): Instruction Suffix Description movdqa перемещение выравненного двойного четверного слова (double quadword) movdqu перемещение невыравненного двойного четверного слова mova [ ps, pd ] перемещение выравненого вещественного movu [ ps, pd ] перемещение невыравненного вещественного movhl [ ps ] перемещение верхней части упакованного вещественного вниз movlh [ ps ] перемещение нижней части упакованного вещественного вверх movh [ ps, pd ] перемещение верхней части упакованного вещественного movl [ ps, pd ] перемещение нижней части упакованного вещественного mov [ d, q, ss, sd ] перемещение скалярных данных lddqu перемещение невыравненного двойного четверного слова mov
Целочисленные арифметические инструкции: Instruction Suffix Description padd [ b, w, d, q ] векторное сложение (знаковые и беззнаковые) psub [ b, w, d, q ] векторное вычитание (знаковые и беззнаковые) padds [ b, w ] векторное сложение с насыщением (знаковые) paddus [ b, w ] векторное сложение с насыщением (беззнаковые) psubs [ b, w ] векторное вычитание с насыщением (знаковые) psubus [ b, w ] векторное вычитание с насыщением (беззнаковые) pmins [ w ] векторный минимум (знаковые) pminu [ b ] векторный минимум (беззнаковый) pmaxs [ w ] векторный максимум (знаковый) pmaxu [ b ] векторный максимум (беззнаковый) 10/17/10
Арифметические операции с вещественными числами: Instruction Suffix Description add [ ss, ps, sd, pd ] сложение div [ ss, ps, sd, pd ] деление min [ ss, ps, sd, pd ] минимум max [ ss, ps, sd, pd ] максимум mul [ ss, ps, sd, pd ] умножение sqrt [ ss, ps, sd, pd ] квадратный корень sub [ ss, ps, sd, pd ] вычитание rcp [ ss, ps] приблизительное обратное rsqrt [ ss, ps] приблизительный обратный квадратный корень Идиоматические арифметические инструкции: Instruction Suffix Description pang [ b, w ] векторное среднее с округлением (беззнаковые) pmulh/pmulhu/pmull [ w ] векторное умножение psad [ bw ] векторная сумма абсолютных разниц (беззнаковые) pmadd [ wd ] векторное умножение и сложение (знаковые) addsub [ ps, pd ] вещественное сложение/вычитание hadd [ ps, pd ] вещественное горизонтальное сложение hsub [ ps, pd ] вещественное горизонтальное вычитание 10/17/10
Логические инструкции: Instruction Suffix Description pand побитовый логический AND pandn побитовый логический AND-NOT por побитовый логический OR pxor побитовый логический XOR and [ ps, pd ] побитовый логический AND andn [ ps, pd ] побитовый логический AND-NOT or [ ps, pd ] побитовый логический OR xor [ ps, pd ] побитовый логический XOR Инструкции сравнения: Instruction Suffix Description pcmp
Конвертирующие инструкции: Instruction Suffix Description packss [wb, dw] упаковывать с насыщением (знаковые) paсkus [wb] упаковывать с насыщением (беззнаковые) cvt конвертация cvtt конвертация с усечением Инструкции сдвига: Instruction Suffix Description psll [ w, d, q, dq ] логический сдвиг влево psra [w, d] арифметический сдвиг вправо psrl [ w, d, q, dq ] правый логический сдвиг Перестановочные (shuffle) инструкции: Instruction Suffix Description pshuf [ w, d ] векторная перестановка pshufh [w] векторная перестановка верхней части pshufl [w] векторная перестановка нижней части ырга [ ps, pd ] перестановка 10/17/10
Распаковывающие инструкции: Instruction Suffix Description punpckh [bw, wd, dq, qdq] распаковать верхнюю часть punpckl [bw, wd, dq, qdq] распаковать нижнюю часть unpckh [ps, pd] распаковать верхнюю часть unpckl [ps, pd] распаковать нижнюю часть Инструкции работы с кэшем: Instruction Suffix Description movnt [ ps, pd, q, dq ] перемещать выравненные не временные данные prefetch
Три набора опций для использования процессорноспецифических расширений 1. Опции –Qx
Типичная векторная инструкция представляет собой операцию над двумя векторами в памяти или в регистрах фиксированной длины. Данные векторные регистры могут быть загружены из памяти за одну операцию или по частям. Описание основ SSE технологии можно прочитать в CHAPTER 10 PROGRAMMING WITH STREAMING SIMD EXTENSIONS (SSE) документа «Intel 64 and IA-32 Intel Architecture Software Developer's Manual» (Volume 1). Microsoft Visual Studio поддерживает набор SSE интринсиков (встроенных функций), которые позволяют напрямую использовать SSE инструкции из С/C++ кода. Это позволяет осуществлять векторизацию вручную. Для этого необходимо подключить xmmintrin. h, который определяет тип __m 128 (это векторный тип) и операции с ним. Допустим, мы хотим векторизовать вручную цикл: for(i=0; i
#include
Полученный прирост производительности - 2. 7 x. Для работы некоторых векторных инструкций необходимо, чтобы адреса в памяти были выравнены на 16. В нашем тесте это получилось случайно, в реальном случае нужно об этом заботиться. Тест, оптимизированный компилятором, показывает следующий результат: icl test. c -Qvec_report 3 -Fetest_intel_opt. exe time test_intel_opt. exe 2. 000000 CPU time for command: 'test_intel_opt. exe' real 0. 328 sec user 0. 313 sec system 0. 000 sec
Допустимость векторизации Векторизация – перестановочная оптимизация. Операции меняют порядок выполнения. Перестановочные оптимизации допустимы, если не изменяется порядок зависимостей. Таким образом мы получили критерий допустимости векторизации в терминах зависимостей. Простейший вариант – зависимостей в векторизуемом цикле нет. Зависимости в векторизуемом цикле есть, но их порядок после векторизации совпадает с порядком в невекторизованном цикле.
/Qvec-report[n] control amount of vectorizer diagnostic information n=0 no diagnostic information n=1 indicate vectorized loops (DEFAULT) n=2 indicate vectorized/non-vectorized loops n=3 indicate vectorized/non-vectorized loops and prohibiting data dependence information n=4 indicate non-vectorized loops n=5 indicate non-vectorized loops and prohibiting data dependence information Использование: icl -c -Qvec_report 3 loop. c Примеры диагностики: C: loopsloop 1. c(5) (col. 1): remark: LOOP WAS VECTORIZED. C: loopsloop 3. c(5) (col. 1): remark: loop was not vectorized: vectorization possible but seems inefficient. C: loopsloop 6. c(5) (col. 1): remark: loop was not vectorized: nonstandard loop is not a vectorization candidate. 10/17/10
Фортран активно используется в описании векторизации, поскольку имеет удобное понятие секции массива. В упрощенном виде: DO I=1, N A(I)=… END DO при векторизации переводится в DO I=1, N/K A(I: I+K)=… END DO где K – число элементов матрицы A, размещаемых в векторном регистре. Наглядным критерием возможности векторизации является то факт, что введение секций массива не порождает зависимостей. DO I=1, N DO I=1, N/K A(I)=A(I)+C => A(I: I+K) = A(I: I+K)+C END DO END DO Может быть векторизован. DO I=1, N DO I=1, N/K A(I+1)=A(I)+C => A(I+1: I+1+K)=A(I: I+K)+C END DO END DO Не может быть векторизован.
PROGRAM TEST_VEC INTEGER, PARAMETER : : N=1000 #ifdef PERF INTEGER, PARAMETER : : P=4 #else INTEGER, PARAMETER : : P=3 #endif INTEGER A(N) DO I=1, N-P A(I+P)=A(I) END DO PRINT *, A(50) END Предположение: Цикл можно векторизовать, если дистанция для зависимости >= K, где K – количество элементов массива, входящих в векторный регистр. Проверяем утверждение с помощью компилятора: ifort test. F 90 -o a. out –vec_report 3 echo ------------------ifort test. F 90 -DPERF -o b. out –vec_report 3. /build. sh test. F 90(11): (col. 1) remark: loop was not vectorized: existence of vector dependence. ------------------test. F 90(11): (col. 1) remark: LOOP WAS VECTORIZED.
Факторы, влияющие на производительность векторизованного кода. Рассмотрим такой тест: INTEGER : : A(1000), B(1000) INTEGER I, K INTEGER, PARAMETER : : REP = 500000 A=2 DO K=1, REP CALL ADD(A, B) END DO PRINT *, SHIFT, B(101) CONTAINS SUBROUTINE ADD(A, B) INTEGER A(1000), B(1000) INTEGER I !DEC$ UNROLL(0) DO I=1, 1000 -SHIFT B(I) = A(I+SHIFT)+1 END DO END SUBROUTINE END
ifort test 1. F 90 -O 2 -Ob 0 /fpp /DSHIFT=0 -Fea. exe -Qvec_report >a. out 2>&1 ifort test 1. F 90 -O 2 -Ob 0 /fpp /DSHIFT=1 -Feb. exe -Qvec_report >b. out 2>&1 %TIME% a. exe %TIME% b. exe Скомпилируем исходный тест, передав в программу разные значения переменной SHIFT. Получим: C: usersaanufriestudentscontinuous 2>C: u 4 winbinx 86_win 32time. exe a. exe 0 3 CPU time for command: 'a. exe' real 0. 125 sec user 0. 094 sec system 0. 000 sec C: usersaanufriestudentscontinuous 2>C: u 4 winbinx 86_win 32time. exe b. exe 1 3 CPU time for command: 'b. exe' real 0. 297 sec user 0. 281 sec system 0. 000 sec
ifort test 1. F 90 -O 2 -Ob 0 /fpp /DSHIFT=0 /Fas -Ob 0 -Qvec_report 3 -S –Fatest 0. s ifort test 1. F 90 -O 2 -Ob 0 /fpp /DSHIFT=1 /Fas -Ob 0 -S -Qvec-report 3 –Fatest 1. s Для данного цикла имеем для быстрого случая: . B 2. 5: ; Preds. B 2. 5. B 2. 4 $LN 83: ; ; ; B(I) = A(I+SHIFT)+1 movdqa xmm 1, XMMWORD PTR [eax+ecx*4] ; 17. 11 $LN 84: paddd xmm 1, xmm 0 ; 17. 4 $LN 85: movdqa XMMWORD PTR [edx+ecx*4], xmm 1 $LN 86: add ecx, 4 ; 16. 3 $LN 87: cmp ecx, 1000 ; 16. 3 $LN 88: jb . B 2. 5 ; Prob 99% ; 16. 3 ; 17. 4
Для данного цикла имеем для медленного случая: . B 2. 5: ; Preds. B 2. 5. B 2. 4 $LN 81: ; ; ; B(I) = A(I+SHIFT)+1 movdqu xmm 1, XMMWORD PTR [4+eax+ecx*4] ; 17. 11 $LN 82: paddd xmm 1, xmm 0 ; 17. 4 $LN 83: movdqa XMMWORD PTR [edx+ecx*4], xmm 1 ; 17. 4 $LN 84: add ecx, 4 ; 16. 3 $LN 85: cmp ecx, 996 ; 16. 3 $LN 86: jb . B 2. 5 ; Prob 99% ; 16. 3 MOVDQA—Move Aligned Double Quadword MOVDQU—Move Unaligned Double Quadword
Производительность векторизованного цикла зависит от того, каким образом векторизуемые объекты расположены в памяти. Поэтому первый аспект, имеющий огромное значение для производительности программы, – выравнивание данных в памяти. Выравнивание структур данных (Data Structure Alignment) это метод, с помощью которого данные располагаются в компьютерной памяти. Это понятие заключает в себе два различных, но взаимосвязанных вопроса: выравнивание данных (Data alignment) и заполнение структур данных (Data structure padding). Выравнивание данных определяет, как те или иные данные расположены относительно границ памяти. Этот факт, как правило, связан с типом данных. Заполнение структур данных подразумевает вставку безымянных полей в структуру данных, для того чтобы сохранить относительное выравнивание полей структуры.
Информация о выравнивании может быть получена с помощью интринсика (встроенной функции) __alignof__. Размер переменной данного типа и выравнивание по умолчанию может зависеть от типа компилятора. printf("int: sizeof=%d align=%dn", sizeof(a), __alignof__(a)); Выравнивание для 32 битного Интеловского C++ компилятора: bool sizeof = 1 alignof = 1 wchar_t sizeof = 2 alignof = 2 short int sizeof = 2 alignof = 2 int sizeof = 4 alignof = 4 long int sizeof = 8 alignof = 8 float sizeof = 4 alignof = 4 double sizeof = 8 alignof = 8 long double sizeof = 8 alignof = 8 void* sizeof = 4 alignof = 4 Те же самые правила используются и при выравнивании массивов. Существует возможность выровнять объект необходимым образом: __declspec(align(16)) float x[N]; 10/17/10
struct foo { bool a; short b; long c; bool d; }; struct foo { bool a; char pad 1[1]; short b; char pad 2[4]; long c; bool d; char pad 3[7]; }; Порядок полей в структуре оказывает влияние на размер объекта производного типа. С целью уменьшения размера объекта рекомендуется размещать поля в объекте в порядке уменьшения их размера. Можно использовать __declspec для выравнивания полей структуры. typedef struct a. Stuct{ __declspec(align(16)) float x[N]; __declspec(align(16)) float y[N]; __declspec(align(16)) float z[N]; }; 10/17/10
Производительность векторизованного цикла зависит от того, как обрабатываются векторные регистры, поэтому в общем случае скалярный цикл, работающий с одним массивом DO I=K, N A(I)=… END DO векторизуется следующим образом: if(! N
Каким образом правильное выравнивание может помочь в случае скалярного цикла, обрабатывающего несколько массивов? void Calculate(float * restrict a, float * restrict b, float * restrict c , int n) { int i; for(i=0; i
Добавив в нашу функцию следующие строки __assume_aligned(a, 16); __assume_aligned(b, 16); __assume_aligned(c, 16); мы получим код без головного выравнивающего цикла, использующий инструкции movaps для доступа к памяти. Оценим выгодность полученного кода, добавив к нашей функции функцию main следующего вида: #include
Векторизация может ухудшить производительность маленьких циклов. INTEGER I, J, A(100) INTEGER, VOLATILE : : K, N K=1 N=11 DO J=1, 1000000 DO I=K, N A(I)=J END DO PRINT *, A(55) END Без векторизации тест выполняется 0. 10 s а с векторизацией 0. 13 s. Почему так происходит? Цикл в примере разложится на 3 части: от 2 до 5 – скалярный цикл (3 итерации) от 5 до 8 – векторизированный цикл (1 итерация) от 9 до 11 – скалярный цикл (3 итерации)
Векторизация в общем случае может быть выполнена не только на внутреннем цикле. Если мы имеем вложенное множество циклов, и внутренний цикл не может быть векторизован из-за существования каких-либо зависимостей, то можно пытаться векторизовать внешний цикл: DO I=1, N DO J=1, K A(J, I) =. . END DO Векторизация DO I=1, N DO J=1, K A(J, I: I+P-1)=… END DO Будет ли полезной такая оптимизация?
Векторизация и многоверсионный код. Дополнительная возможность для улучшения производительности – формирование многоверсионного кода, т. е. кода, включающего проверки времени выполнения. Примеры случаев, когда это может работать: void sub(int *a, int *b) { int i; for(i=0; i<1000; i++) a[i]=b[i]; } void sub(int *a, int n) { int i; for(i=0; i<1000; i++) a[i]=a[i+n]; } Можно проверить, работает ли многоверсионная векторизация для gcc или интеловского компилятора.
Существуют полезные прагмы (pragma), которые вы можете использовать для улучшения эффективности векторизации: __declspec(align(n)) – указывает компилятору выравнивать переменную на n-байтовую границу. #pragma ivdep - предлагает игнорировать предполагаемую векторную зависимость. #pragma vector{aligned|unaligned|always} – определяет, как векторизивать цикл. #pragma novector - цикл не должен быть векторизован. Аналоги для Фортрана: !DEC$ IVDEP !DEC$ VECTOR ALWAYS !DEC$ NOVECTOR
Компилятор Intel пытается объединить оптимизации циклических конструкций, векторизацию, распараллеливание и оценочную модель в одном компоненте, для того чтобы достичь наилучшего взаимодействия между этими оптимизациями. Сначала оптимизаций компилятор пытается улучшить работу с памятью, разбивает циклы, переставляет, векторизует их, развертывает, объединяет. Векторизуется только внутренний цикл.
Спасибо за внимание! 10/17/10


