- PVSM.RU - https://www.pvsm.ru -
Итак, фактическую разработку новых оптимизаций в GCC 5.0 можно считать законченной. Продукт GCC 5.0 находится сейчас в фазе stage3 [1], то есть идет доработка уже внедренных оптимизаций. В данной и последующих статьях я расскажу об оптимизациях, реализованных в 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].
2. Работа с N-мерными координатами (скажем, чтобы нормализовать трехмерные точки). Пример [3].
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)
Далее приведены таблицы, позволяющие оценить увеличение производительности при использовании 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;
}
Где:
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};
Такая матрица используется чтобы минимизировать вычисления внутри цикла до сравнительно быстрых сложений и вычитаний.
Опции компиляции "-Ofast" плюс "-march=slm" для Silvermont [4], "-march=core-avx2" для Haswell [5] и все комбинации -DLDGSIZE={1,2,3,4,8} -DSTGSIZE={1,2,3,4,8}
Прирост производительности GCC 5.0 по сравнению с 4.9 (во сколько раз ускорилось, больше — лучше).
Silvermont [4]: Intel® Atom(TM) CPU C2750 @ 2.41GHz
Прирост до 6.5 раз!

Как видно из таблицы, результаты для групп сохранений в память длиной 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 [5], стоит отметить, что большинство констант ".LC" будут инвариантами в цикле, однако только при наличии свободных регистров. В противном случае их придется ставить непосредственно в phufb: «pshufb .LC0, %xmm3». Такой pshufb будет больше на размер адреса и потенциально дольше исполнятся.
Haswell [5]: Intel® Core(TM) i7-4770K CPU @ 3.50GHz
Прирост до 3 раз!

В данном случае результаты для групп сохранений в память длиной 3 намного лучше, так как на Haswell [5] инструкция «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.
Компиляторы используемые в замерах:
Скачать пример, на котором производились замеры, можно из оригинального текста статьи на английском [8].
Автор: Evgeny1982
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/76146
Ссылки в тексте:
[1] stage3: https://gcc.gnu.org/develop.html
[2] Пример: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52252
[3] Пример: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61403
[4] Silvermont: http://en.wikipedia.org/wiki/Silvermont
[5] Haswell: https://ru.wikipedia.org/wiki/Haswell
[6] GCC 4.9: https://gcc.gnu.org/gcc-4.9
[7] GCC 5.0 trunk, ревизия 218160: https://gcc.gnu.org/viewcvs/gcc/trunk/?pathrev=218160
[8] оригинального текста статьи на английском: https://software.intel.com/en-us/blogs/2014/11/24/what-is-new-for-x86-in-upcoming-gcc-50
[9] Источник: http://habrahabr.ru/post/244137/
Нажмите здесь для печати.