Когда компилятор переупорядочивает инструкции AVX на Sandy, это влияет на производительность?
Пожалуйста, не говорите, что это преждевременная микрооптимизация. Я хочу понять, насколько это возможно, учитывая мои ограниченные знания, как работает описанная функция и сборка SB, и убедитесь, что мой код использует эту архитектурную функцию. Благодарим вас за понимание.
Я начал учиться intrinsics несколько дней назад, поэтому ответ может показаться очевидным для некоторых, но у меня нет надежного источника информации, чтобы понять это.
Мне нужно оптимизировать некоторый код для CPU Sandy Bridge (это требование). Теперь я знаю, что он может сделать один AVX умножить и добавить один AVX за цикл и прочитать эту статью:
http://research.colfaxinternational.com/file.axd?file=2012%2F7%2FColfax_CPI.pdf
который показывает, как это можно сделать на С++. Таким образом, проблема в том, что мой код не будет автоматически сгенерирован с использованием Intel-компилятора (что является еще одним требованием к задаче), поэтому я решил реализовать его вручную, используя встроенные функции следующим образом:
__sum1 = _mm256_setzero_pd();
__sum2 = _mm256_setzero_pd();
__sum3 = _mm256_setzero_pd();
sum = 0;
for(kk = k; kk < k + BS && kk < aW; kk+=12)
{
const double *a_addr = &A[i * aW + kk];
const double *b_addr = &newB[jj * aW + kk];
__aa1 = _mm256_load_pd((a_addr));
__bb1 = _mm256_load_pd((b_addr));
__sum1 = _mm256_add_pd(__sum1, _mm256_mul_pd(__aa1, __bb1));
__aa2 = _mm256_load_pd((a_addr + 4));
__bb2 = _mm256_load_pd((b_addr + 4));
__sum2 = _mm256_add_pd(__sum2, _mm256_mul_pd(__aa2, __bb2));
__aa3 = _mm256_load_pd((a_addr + 8));
__bb3 = _mm256_load_pd((b_addr + 8));
__sum3 = _mm256_add_pd(__sum3, _mm256_mul_pd(__aa3, __bb3));
}
__sum1 = _mm256_add_pd(__sum1, _mm256_add_pd(__sum2, __sum3));
_mm256_store_pd(&vsum[0], __sum1);
Причина, по которой я вручную разворачиваю цикл, как это объясняется здесь:
Развертывание Loop для достижения максимальной пропускной способности с помощью Ivy Bridge и Haswell
Говорят, вам нужно разворачиваться в 3 раза, чтобы добиться лучшей производительности на Сэнди. Мое наивное тестирование подтверждает, что это действительно работает лучше, чем без разворачивания или 4-кратного разворота.
ОК, так вот проблема. Компилятор icl от Intel Parallel Studio 15 генерирует это:
$LN149:
movsxd r14, r14d ;78.49
$LN150:
vmovupd ymm3, YMMWORD PTR [r11+r14*8] ;80.48
$LN151:
vmovupd ymm5, YMMWORD PTR [32+r11+r14*8] ;84.49
$LN152:
vmulpd ymm4, ymm3, YMMWORD PTR [r8+r14*8] ;82.56
$LN153:
vmovupd ymm3, YMMWORD PTR [64+r11+r14*8] ;88.49
$LN154:
vmulpd ymm15, ymm5, YMMWORD PTR [32+r8+r14*8] ;86.56
$LN155:
vaddpd ymm2, ymm2, ymm4 ;82.34
$LN156:
vmulpd ymm4, ymm3, YMMWORD PTR [64+r8+r14*8] ;90.56
$LN157:
vaddpd ymm0, ymm0, ymm15 ;86.34
$LN158:
vaddpd ymm1, ymm1, ymm4 ;90.34
$LN159:
add r14d, 12 ;76.57
$LN160:
cmp r14d, ebx ;76.42
$LN161:
jb .B1.19 ; Prob 82% ;76.42
Для меня это похоже на беспорядок, где исправлен правильный порядок (добавьте рядом с умножением, требуемым для использования удобной функции SB).
Вопрос:
-
Будет ли этот код сборки использовать функцию Sandy Bridge, о которой я говорю?
-
Если нет, что мне нужно сделать, чтобы использовать эту функцию и не допустить, чтобы код стал "запутанным" следующим образом?
Кроме того, когда есть только одна итерация цикла, порядок является хорошим и чистым, то есть загружать, умножать, добавлять, как и должно быть.
Ответы
Ответ 1
С процессорами x86 многие люди ожидают получить максимальные FLOPS от точечного продукта
for(int i=0; i<n; i++) sum += a[i]*b[i];
но это оказывается не в этом.
Что может дать максимальный FLOPS, это
for(int i=0; i<n; i++) sum += k*a[i];
где k
- константа. Почему CPU не оптимизирован для точечного продукта? Я могу спекулировать. Оптимизированы для оптимизации процессоров BLAS. BLAS рассматривает строительный блок многих других процедур.
Процедуры BLAS уровня 1 и уровня 2 становятся границами полосы пропускания памяти при увеличении n
. Это только подпрограммы Level-3 (например, Matrix Multiplication), которые могут быть связаны с вычислением. Это связано с тем, что вычисления Уровня 3 идут как n^3
, а чтение - как n^2
. Таким образом, CPU оптимизирован для подпрограмм уровня 3. Процедуры уровня 3 не нуждаются в оптимизации для одного точечного продукта. Им нужно читать только одну матрицу за итерацию (sum += k*a[i]
).
Из этого можно сделать вывод, что количество бит, которые необходимо читать каждому циклу для получения максимальных FLOPS для подпрограмм уровня 3, это
read_size = SIMD_WIDTH * num_MAC
где num_MAC - это число операций умножения с накоплением, которые могут выполняться в каждом цикле.
SIMD_WIDTH (bits) num_MAC read_size (bits) ports used
Nehalem 128 1 128 128-bits on port 2
Sandy Bridge 256 1 256 128-bits port 2 and 3
Haswell 256 2 512 256-bits port 2 and 3
Skylake 512 2 1024 ?
Для Nehalem-Haswell это согласуется с тем, на что способно аппаратное обеспечение. На самом деле я не знаю, что Skylake сможет читать 1024 бит за такт, но если он не может быть AVX512 не очень интересным, я уверен в своей догадке. Хороший участок для Nahalem, Sandy Bridge и Haswell для каждого порта можно найти на http://www.anandtech.com/show/6355/intels-haswell-architecture/8
До сих пор я игнорировал цепи задержки и зависимостей. Чтобы действительно получить максимальные FLOPS, вам нужно развернуть цикл по крайней мере три раза на Sandy Bridge (я использую четыре, потому что мне неудобно работать с краткими тремя)
Лучший способ ответить на ваш вопрос о производительности - найти теоретическую лучшую производительность, которую вы ожидаете для своей операции, а затем сравнить, насколько близко к ней подходит ваш код. Я называю это эффективностью. Сделав это, вы обнаружите, что, несмотря на переупорядочение инструкций, которые вы видите в сборке, производительность все еще хороша. Но есть много других тонких проблем, которые вам могут потребоваться. Вот три проблемы, с которыми я столкнулся:
l1-memory-bandwidth-50-drop-in-efficiency-using-addresses-which-differ-by-4096.
get-peak-bandwidth-on-haswell-in-the-l1-cache-only-getting-62%
difference-in-performance-between-msvc-and-gcc-for-highly-optimized-matrix-multp.
Я также предлагаю вам использовать IACA для изучения производительности.