Ответ 1
Было бы неплохо, если бы мы могли написать код один раз и скомпилировать его для более широких векторов с помощью лишь небольшой настройки, даже в тех случаях, когда автоинъекция не делает трюк.
Я получил тот же результат, что и @hirschhornsalz: массивный, неэффективный код при создании экземпляра с векторами размером больше HW-поддерживаемых векторных размеров. например построение A<8>
без AVX512 создает лодку с 64-битными mov
и vmovsd
инструкциями. Он выполняет одно вещание в локальном стеке, а затем считывает все эти значения отдельно и записывает их в буфер возврата структуры вызывающего.
Для x86, мы можем получить gcc, чтобы испускать оптимальные трансляции для функции, которая принимает аргумент double
(в xmm0) и возвращает вектор (в x/y/zmm0), за стандартные соглашения о вызовах:
- SSE2:
unpckpd xmm0, xmm0
- SSE3:
movddup xmm0, xmm0
- AVX:
vmovddup xmm0, xmm0 / vinsertf128 ymm0, ymm0, xmm0, 1
(AVX1 включает только формуvbroadcastsd ymm, m64
, которая предположительно, будут использоваться, если они включены при вызове данных в памяти) - AVX2:
vbroadcastsd ymm0, xmm0
- AVX512:
vbroadcastsd zmm0, xmm0
. (Обратите внимание, что AVX512 может транслироваться из памяти на лету:VADDPD zmm1 {k1}{z}, zmm2, zmm3/m512/m64bcst{er}
{k1}{z}
означает, что он может использовать регистр маски в качестве слияния или нулевой маски в результате.m64bcst
означает 64-битный адрес памяти, который будет транслироваться.{er}
означает, что режим округления MXCSR может быть переопределен для этой одной инструкции.
IDK, если gcc будет использовать этот режим широковещательной адресации, чтобы сбросить широковещательные загрузки в операнды памяти.
Однако gcc также понимает перетасовку и имеет __builtin_shuffle
для произвольных размеров вектора. С маской постоянной времени компиляции all-zeros тасование становится широковещательной передачей, которая gcc использует наилучшую инструкцию для задания.
typedef int64_t v4di __attribute__ ((vector_size (32)));
typedef double v4df __attribute__ ((vector_size (32)));
v4df vecinit4(double v) {
v4df v_sse;
typeof (v_sse) v_low = {v};
v4di shufmask = {0};
v_sse = __builtin_shuffle (v_low, shufmask );
return v_sse;
}
В функциях шаблона gcc 4.9.2, похоже, имеет проблему, признающую, что оба вектора имеют одинаковую ширину и количество элементов и что маска является вектором int. Это ошибки даже без создания экземпляра шаблона, поэтому, возможно, поэтому у него проблемы с типами. Все работает отлично, если я копирую класс и отформатировал его до определенного размера вектора.
template<int D> struct A{
typedef double dvec __attribute__ ((vector_size (8*D)));
typedef int64_t ivec __attribute__ ((vector_size (8*D)));
dvec v_sse; // typeof(v_sse) is buggy without this typedef, in a template class
A(double v) {
#ifdef SHUFFLE_BROADCAST // broken on gcc 4.9.2
typeof(v_sse) v_low = {v};
//int64_t __attribute__ ((vector_size (8*D))) shufmask = {0};
ivec shufmask = {0, 0};
v_sse = __builtin_shuffle (v_low, shufmask); // no idea why this doesn't compile
#else
typeof (v_sse) zero = {0, 0};
v_sse = zero + v; // doesn't optimize away without -ffast-math
#endif
}
};
/* doesn't work:
double vec2val __attribute__ ((vector_size (16))) = {v, v};
double vec4val __attribute__ ((vector_size (32))) = {v, v, v, v};
v_sse = __builtin_choose_expr (D == 2, vec2val, vec4val);
*/
Мне удалось получить gcc во внутреннем компиляторе при компиляции с -O0
. Векторы + шаблоны, похоже, нуждаются в некоторой работе. (По крайней мере, он вернулся в gcc 4.9.2, который Ubuntu в настоящее время отправляет. Upstream, возможно, улучшился.)
Первая идея, которую я получил, которую я оставил в качестве резервной копии, потому что shuffle не компилируется, заключается в том, что gcc неявно транслируется, когда вы используете оператор с вектором и скаляром. Так, например, добавление скаляра в вектор всех нулей сделает трюк.
Проблема в том, что фактическое добавление не будет оптимизировано, если вы не используете -ffast-math
. -funsafe-math-optimizations
, к сожалению, требуется не только -fno-signaling-nans
. Я попробовал альтернативы +
, которые не могут вызывать исключения FPU, такие как ^
(xor) и |
(или), но gcc не будет выполнять действия на double
s. Оператор ,
не создает векторный результат для scalar , vector
.
Это можно обойти, специализируясь на шаблоне с простыми списками инициализаторов. Если вы не можете получить хороший конструктор generic для работы, я предлагаю отказаться от определения, чтобы вы получили ошибку компиляции, когда нет специализации.
#ifndef NO_BROADCAST_SPECIALIZE
// specialized versions with initializer lists to work efficiently even without -ffast-math
// inline keyword prevents an actual definition from being emitted.
template<> inline A<2>::A (double v) {
typeof (v_sse) val = {v, v};
v_sse = val;
}
template<> inline A<4>::A (double v) {
typeof (v_sse) val = {v, v, v, v};
v_sse = val;
}
template<> inline A<8>::A (double v) {
typeof (v_sse) val = {v, v, v, v, v, v, v, v};
v_sse = val;
}
template<> inline A<16>::A (double v) { // AVX1024 or something may exist someday
typeof (v_sse) val = {v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v};
v_sse = val;
}
#endif
Теперь, чтобы проверить результаты:
// vecinit4 (from above) included in the asm output too.
// instantiate the templates
A<2> broadcast2(double val) { return A<2>(val); }
A<4> broadcast4(double val) { return A<4>(val); }
A<8> broadcast8(double val) { return A<8>(val); }
Выход компилятора (отключены директивы ассемблера):
g++ -DNO_BROADCAST_SPECIALIZE -O3 -Wall -mavx512f -march=native vec-gcc.cc -S -masm=intel -o-
_Z8vecinit4d:
vbroadcastsd ymm0, xmm0
ret
_Z10broadcast2d:
vmovddup xmm1, xmm0
vxorpd xmm0, xmm0, xmm0
vaddpd xmm0, xmm1, xmm0
ret
_Z10broadcast4d:
vbroadcastsd ymm1, xmm0
vxorpd xmm0, xmm0, xmm0
vaddpd ymm0, ymm1, ymm0
ret
_Z10broadcast8d:
vbroadcastsd zmm0, xmm0
vpxorq zmm1, zmm1, zmm1
vaddpd zmm0, zmm0, zmm1
ret
g++ -O3 -Wall -mavx512f -march=native vec-gcc.cc -S -masm=intel -o-
# or g++ -ffast-math -DNO_BROADCAST_SPECIALIZE blah blah.
_Z8vecinit4d:
vbroadcastsd ymm0, xmm0
ret
_Z10broadcast2d:
vmovddup xmm0, xmm0
ret
_Z10broadcast4d:
vbroadcastsd ymm0, xmm0
ret
_Z10broadcast8d:
vbroadcastsd zmm0, xmm0
ret
Обратите внимание, что метод shuffle должен работать нормально, если вы не создаете шаблон, но вместо этого используете только один векторный размер в своем коде. Поэтому переход от SSE к AVX так же просто, как изменение от 16 до 32 в одном месте. Но тогда вам нужно будет скомпилировать один и тот же файл несколько раз, чтобы сгенерировать версию SSE и версию AVX, которую вы могли бы отправить во время выполнения. (Возможно, вам все равно понадобится 128-битная версия SSE, которая не использует кодировку инструкций VEX.)