Новые оптимизации для х86 в ожидаемом GCC 5.0

в 6:35, , рубрики: gcc, open source, Блог компании Intel, векторизация, Программирование

Итак, фактическую разработку новых оптимизаций в GCC 5.0 можно считать законченной. Продукт GCC 5.0 находится сейчас в фазе stage3, то есть идет доработка уже внедренных оптимизаций. В данной и последующих статьях я расскажу об оптимизациях, реализованных в GCC 5.0 для х86 и об их влиянии на производительность программ для процессоров линейки Intel Atom и Intel Core. Сегодня речь пойдет о векторизации групповых обращений в память. В последующих статьях я расскажу об ускорениях в 32-битном PIC режиме и дополнительном усилении векторизации.

Как вы уже, наверное, догадались, в GCC 5.0 существенно улучшена векторизация групповых обращений в память. Под групповым обращением в память понимается итерируемая последовательность обращений. Например:

x = a[i], y = a[i + 1], z = a[i + 2]

итерируемое по i — это группа загрузок из памяти длиной 3. Длина группы обращений в память — это расстояние между самым младшим и старшим адресами в группе. Для примера выше — это (i + 2) – (i) + 1 = 3
Количество обращений в память в группе не превосходит ее длину. Например:

x = a[i], z = a[i + 2]

итерируемое по i — это группа длиной 3, несмотря на то, что обращений в память всего 2.

GCC 4.9 векторизует группы, где длина — это степень 2 (2, 4, 8 …).

GCC 5.0 векторизует группы, где длина равна 3 или степени 2 (2, 4, 8 …). Другие длины не векторизуются, так как встречаются в реальных приложениях очень редко.

Чаще всего векторизация группы обращений в память применяется при работе с массивами структур.

1. Конвертация изображений, скажем, в структуре RGB. Пример.
2. Работа с N-мерными координатами (скажем, чтобы нормализовать трехмерные точки). Пример.
3. Умножение векторов на константную матрицу:

a[i][0] = 7 * b[i][0] - 3 * b[i][1];
a[i][1] = 2 * b[i][0] + b[i][1];

В целом в GCC 5.0 (по сравнению с 4.9)

  • Появилась векторизация группы обращений в память длиной 3
  • Существенно улучшилась векторизации группы загрузок из памяти для любой длины
  • Стали использоваться техники векторизации групп обращений в память оптимальные для конкретного x86 процессора.

Далее приведены таблицы, позволяющие оценить увеличение производительности при использовании GCC 5.0 на байтовых структурах (наибольшее количество элементов в векторе) по сравнению с GCC 4.9. Для оценки используется следующий цикл:

  int i, j, k;  
  byte *in = a, *out = b; 
  for (i = 0; i < 1024; i++) 
    { 
      for (k = 0; k < STGSIZE; k++) 
        { 
          byte s = 0; 
          for (j = 0; j < LDGSIZE; j++) 
            s += in[j] * c[j][k]; 
          out[k] = s; 
        } 
      in += LDGSIZE; 
      out += STGSIZE; 
    } 

Где:

  • c — это константная матрица:


const byte c[8][8] = {1, -1, 1, -1, 1, -1, 1, -1, 
                      1, 1, -1, -1, 1, 1, -1, -1, 
                      1, 1, 1, 1, -1, -1, -1, -1, 
                      -1, 1, -1, 1, -1, 1, -1, 1, 
                      -1, -1, 1, 1, -1, -1, 1, 1, 
                      -1, -1, -1, -1, 1, 1, 1, 1, 
                      -1, -1, -1, 1, 1, 1, -1, 1, 
                      1, -1, 1, 1, 1, -1, -1, -1}; 

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

  • in и out — указатели на глобальные массивы «a[1024 * LDGSIZE]» и «b[1024 * STGSIZE]»
  • byte это unsigned char
  • LDGSIZE и STGSIZE – макросы, определяющие длину группы загрузок из памяти и сохранений в память соответственно

Опции компиляции "-Ofast" плюс "-march=slm" для Silvermont, "-march=core-avx2" для Haswell и все комбинации -DLDGSIZE={1,2,3,4,8} -DSTGSIZE={1,2,3,4,8}

Прирост производительности GCC 5.0 по сравнению с 4.9 (во сколько раз ускорилось, больше — лучше).

Silvermont: Intel® Atom(TM) CPU C2750 @ 2.41GHz

Прирост до 6.5 раз!

image

Как видно из таблицы, результаты для групп сохранений в память длиной 3 не очень хорошие. Это происходит потому, что для такого преобразования требуется 8 инструкций «pshufb», длительность исполнения которой на Silvermont около 5 тактов. Несмотря на это, векторизация других команд в цикле может дать больший прирост. Это видно на примере группы загрузок из памяти длиной 2, 3, 4 и 8.

Пример GCC ассемблера для группы загрузок из памяти длиной 2:

GCC 4.9 GCC 5.0
movdqa .LC0(%rip), %xmm7
movdqa .LC1(%rip), %xmm6
movdqa .LC2(%rip), %xmm5
movdqa .LC3(%rip), %xmm4
movdqu a(%rax,%rax), %xmm1
movdqu a+16(%rax,%rax), %xmm0
movdqa %xmm1, %xmm3
pshufb %xmm7, %xmm3
movdqa %xmm0, %xmm2
pshufb %xmm5, %xmm1
pshufb %xmm6, %xmm2
pshufb %xmm4, %xmm0
por %xmm2, %xmm3
por %xmm0, %xmm2
movdqa .LC0(%rip), %xmm3
movdqu a(%rax,%rax), %xmm0
movdqu a+16(%rax,%rax), %xmm1
movdqa %xmm3, %xmm4
pand %xmm0, %xmm3
psrlw $8, %xmm0
pand %xmm1, %xmm4
psrlw $8, %xmm1
packuswb %xmm4, %xmm3
packuswb %xmm1, %xmm0

Здесь, как и в приведенном ниже примере для Haswell, стоит отметить, что большинство констант ".LC" будут инвариантами в цикле, однако только при наличии свободных регистров. В противном случае их придется ставить непосредственно в phufb: «pshufb .LC0, %xmm3». Такой pshufb будет больше на размер адреса и потенциально дольше исполнятся.

Haswell: Intel® Core(TM) i7-4770K CPU @ 3.50GHz

Прирост до 3 раз!

image

В данном случае результаты для групп сохранений в память длиной 3 намного лучше, так как на Haswell инструкция «pshufb» исполняется за 1 такт. Более того, наибольший прирост здесь именно для внедренной векторизации групп обращений в память длиной 3.

Пример GCC ассемблера для группы загрузок из памяти длиной 2:

GCC 4.9 GCC 5.0
vmovdqa .LC0(%rip), %ymm7
vmovdqa .LC1(%rip), %ymm6
vmovdqa .LC2(%rip), %ymm5
vmovdqa .LC3(%rip), %ymm4
vmovdqu a(%rax,%rax), %ymm0
vmovdqu a+32(%rax,%rax), %ymm2
vpshufb %ymm7, %ymm0, %ymm1
vpshufb %ymm5, %ymm0, %ymm0
vpshufb %ymm6, %ymm2, %ymm3
vpshufb %ymm4, %ymm2, %ymm2
vpor %ymm3, %ymm1, %ymm1
vpor %ymm2, %ymm0, %ymm0
vpermq $216, %ymm1, %ymm1
vpermq $216, %ymm0, %ymm0
vmovdqa .LC0(%rip), %ymm3
vmovdqu a(%rax,%rax), %ymm0
vmovdqu a+32(%rax,%rax), %ymm2
vpand %ymm0, %ymm3, %ymm1
vpsrlw $8, %ymm0, %ymm0
vpand %ymm2, %ymm3, %ymm4
vpsrlw $8, %ymm2, %ymm2
vpackuswb %ymm4, %ymm1, %ymm1
vpermq $216, %ymm1, %ymm1
vpackuswb %ymm2, %ymm0, %ymm0
vpermq $216, %ymm0, %ymm0

Из всего вышесказанного следует вывод: используя GCC 5.0, можно существенно ускорить производительность приложений, подобных описанным выше. Начинать пробовать можно уже сейчас! Большинство правок планируется портировать в Android NDK.

Компиляторы используемые в замерах:

Скачать пример, на котором производились замеры, можно из оригинального текста статьи на английском.

Автор: Evgeny1982

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js