Pull to refresh

Comments 5

 for (int i = 0; i < 1024; i += 4) {
    sum0 += a[i    ];
    sum1 += a[i + 1];
    sum2 += a[i + 2];
    sum3 += a[i + 3];
  }
  return sum0 + sum1 + sum2 + sum3;

А такая конструкция легко поддаётся векторизации.

такую конструкцию даже векторизировать не надо, она прекрасно работает в 4 раза быстрее чем обычное инкрементирование, т.к. давно в ядрах процессора блоки суммирования и другие продублированы (и их как раз штуки 3-4 минимум) и процессор без проблем выполнит 4 сложения за условный такт если нет зависимости по данным. чтобы извлечь заметный плюс от векторизации для простого суммирования нужно складывать не 4 а 8 интов, или байты, их в AVX много влезет, а обычных сумматоров всё же меньше будет.

4 сложения подряд действительно быстрее чем 4*время на одно сложение, но все таки медленнее чем одна SSE инструкция, ьак что векторизовать стоит.

Ну вот зачем писать, если сами не разбираетесь? В современных x64 ядрах по два функциональных устройства для сложения-умножения чисел с плавающей точкой, а не "минимум 3-4". Вы можете в этом убедиться, посмотрев диаграму ядра Skylake (https://en.wikichip.org/w/images/7/7e/skylake_block_diagram.svg), и поискав там прямоугольнички с текстом "FP FMA". Либо можете зайти на uops.info (https://uops.info/table.html), и увидеть что на всех современных микроархитектурах у сложения пропускная способность 0.5 (то есть они делают два сложения за такт).

Автор статьи верно утверждает, что такая конструкция поддается векторизации, потому что понимает, что использование четырех независимых аккумуляторов никак не связано с векторизацией. Действительно, те самые "FP FMA" - это векторные устройства, которые могут складывать по 128/256/512-битному вектору за раз. Вот так будет выглядеть этот цикл, если его векторизовать (для примера используем SSE):

__m128 sum0 = _mm_setzero_ps();
__m128 sum1 = _mm_setzero_ps();
__m128 sum2 = _mm_setzero_ps();
__m128 sum3 = _mm_setzero_ps();

for (int i = 0; i < 1024; i += 16) {
    _mm_add_ps(sum0, _mm_load_ps(a + i + 0));
    _mm_add_ps(sum1, _mm_load_ps(a + i + 4));
    _mm_add_ps(sum2, _mm_load_ps(a + i + 8));
    _mm_add_ps(sum3, _mm_load_ps(a + i + 12));
}

_mm_add_ps(sum0, sum1);
_mm_add_ps(sum2, sum3);
_mm_add_ps(sum0, sum2);

// TODO: складываем четыре линии sum0, получаем результат

Наконец, почему именно четыре аккумулятора? Я вообще не понял что вы написали (да вы и сами не поняли, скорее всего). Настоящий ответ тут такой: в Skylake (опять же, для примера) сложение имеет глубину конвейера, равную четырем (см uops.info), то есть одновременно "in-flight" может находится четыре сложения, и в таком случае конвейер будет полностью загружен. Однако, так как функциональных устройства два, правильнее будет использовать восемь аккумуляторов. У какого-нибудь Zen2, например, латентность уже три, а не четыре, а значит аккумуляторов нужно делать шесть.

а ну да, FP 2, а целочисленных 4, спутал здесь.

спасибо за интересный комментарий!

Почти. Скалярных целочисленных ALU действительно четыре. А вот векторных три (см. опять же диаграму на wikichips "INT Vect ALU" либо инструкцию padd на uops.info)

Sign up to leave a comment.