Ответ 1
Вы можете использовать 2x _mm256_permute2f128_ps
для выравнивания низких и высоких полос для вертикального vaddps
. Это вместо 2x extractf128
/insertf128
. Это также превращает две команды 128b vaddps xmm
в один 256b vaddps ymm
.
vperm2f128
работает так же быстро, как один vextractf128
или vinsertf128
на процессорах Intel. Однако он замедляется на AMD (8 м-операционных систем с задержкой 4 с на семействе Bulldozer). Тем не менее, не так уж плохо, что вам нужно его избегать, даже если вы заботитесь о перфомансе на AMD. (И одна из перестановок на самом деле может быть vinsertf128
).
__m256 hsum8(__m256 a, __m256 b, __m256 c, __m256 d,
__m256 e, __m256 f, __m256 g, __m256 h)
{
// a = [ A7 A6 A5 A4 | A3 A2 A1 A0 ]
__m256 sumab = _mm256_hadd_ps(a, b);
__m256 sumcd = _mm256_hadd_ps(c, d);
__m256 sumef = _mm256_hadd_ps(e, f);
__m256 sumgh = _mm256_hadd_ps(g, h);
__m256 sumabcd = _mm256_hadd_ps(sumab, sumcd); // [ D7:4 ... A7:4 | D3:0 ... A3:0 ]
__m256 sumefgh = _mm256_hadd_ps(sumef, sumgh); // [ H7:4 ... E7:4 | H3:0 ... E3:0 ]
__m256 sum_hi = _mm256_permute2f128_ps(sumabcd, sumefgh, 0x31); // [ H7:4 ... E7:4 | D7:4 ... A7:4 ]
__m256 sum_lo = _mm256_permute2f128_ps(sumabcd, sumefgh, 0x20); // [ H3:0 ... E3:0 | D3:0 ... A3:0 ]
__m256 result = _mm256_add_ps(sum_hi, sum_lo);
return result;
}
Этот компилируется, как вы ожидали. Второй permute2f128
фактически компилируется в vinsertf128
, поскольку он использует только нижнюю полосу каждого входа таким же образом, что и vinsertf128
. gcc 4.7 и позже делают эту оптимизацию, но только более поздние версии clang (v3.7). Если вы заботитесь о старом кланге, сделайте это на исходном уровне.
Экономия в исходных линиях больше, чем экономия в инструкциях, потому что _mm256_extractf128_ps(sumabcd, 0);
скомпилирует нулевые инструкции: это просто бросок. Никакой компилятор не должен выделять vextractf128
с помощью imm8, кроме 1
. (vmovdqa xmm/m128, xmm
всегда лучше для получения низкой полосы). Хорошая работа Intel по расходованию байта инструкции на будущую проверку, которую вы не могли использовать, потому что простые префиксы VEX не имеют места для кодирования более длинных векторов.
Две команды vaddps xmm
могут выполняться параллельно, поэтому использование одиночного vaddps ymm
в основном - это просто пропускная способность (и размер кода), а не латентность.
Мы сбрасываем 3 цикла из полного исключения окончательного vinsertf128
.
vhaddps
- это 3 часа, 5 секунд ожидания и 1 на 2 c пропускную способность. (Латентность 6 с на Skylake). Два из этих трех пусков работают на порту тасования. Я предполагаю, что он в основном делает 2x shufps
для генерации операндов для addps
.
Если мы сможем эмулировать haddps
(или, по крайней мере, получить горизонтальную операцию, которую мы можем использовать) с одним shufps
/addps
или чем-то, мы бы вышли вперед. К сожалению, я не понимаю, как это сделать. Единственный случайный выбор может дать только один результат с данными из двух векторов, но нам нужны оба входа в вертикальный addps
, чтобы иметь данные от обоих векторов.
Я не думаю, что выполнение горизонтальной суммы выглядит по-другому перспективным. Как правило, hadd не является хорошим выбором, поскольку обычный случай использования в горизонтальной сумме касается только одного элемента его вывода. Это не так: каждый элемент каждого результата hadd
фактически используется.