Вы можете применить vblendps
к результату умножения для диагональных блоков. Это очень легко при развертывании 8 л oop итераций. Развернув, вы также сохраняете перезагрузку вашего вектора каждый раз. После этого просто умножьте + добавьте к нему оставшиеся блоки (с FMA, если доступно). И, наконец, уменьшите векторные результаты до одного вектора (это один из немногих случаев, когда haddps
может быть полезным).
Вот версия, которая предполагает, что ваш размер кратен 8 (а ваш матрица квадратная). Он либо добавляет к существующему вектору result
, либо «добавляет» к неявному нулевому вектору. Я добавил _matArray
в качестве параметра функции, и я предполагаю, что _x
, _y
и fVecCount
в вашем примере одинаковы. Не проверено / протестировано ( ", но он компилируется" ) и достаточно много кода для копирования + вставки (вы можете попытаться автоматически развернуть части этого компилятора). Что касается локальности кэша, может быть лучше развернуть 4 строки (8 *1024* 4B = 32 КБ, что больше, чем кэш L1 Piledriver). Или может помочь добавление некоторых предварительных выборок.
void dot(float* result, float const* _matArray, float const* f_vecStart) {
bool const add_to_result = true; // can also be a template parameter
int const rowlength = 1024; // can also be a function parameter
for (int row = 0; row < rowlength; row += 8) {
float const* blockStart = _matArray + rowlength * row;
// registers for accumulation
__m256 s0, s1, s2, s3, s4, s5, s6, s7;
// calculate diagonal-block:
{
// register to blend out or replace value by pre-existing value
__m256 res0 = add_to_result ? _mm256_loadu_ps(result + row) : _mm256_setzero_ps();
__m256 vec_r = _mm256_loadu_ps(f_vecStart + row);
s0 = _mm256_mul_ps(vec_r, _mm256_loadu_ps(blockStart + row + 0 * rowlength));
s1 = _mm256_mul_ps(vec_r, _mm256_loadu_ps(blockStart + row + 1 * rowlength));
s2 = _mm256_mul_ps(vec_r, _mm256_loadu_ps(blockStart + row + 2 * rowlength));
s3 = _mm256_mul_ps(vec_r, _mm256_loadu_ps(blockStart + row + 3 * rowlength));
s4 = _mm256_mul_ps(vec_r, _mm256_loadu_ps(blockStart + row + 4 * rowlength));
s5 = _mm256_mul_ps(vec_r, _mm256_loadu_ps(blockStart + row + 5 * rowlength));
s7 = _mm256_mul_ps(vec_r, _mm256_loadu_ps(blockStart + row + 7 * rowlength));
s6 = _mm256_mul_ps(vec_r, _mm256_loadu_ps(blockStart + row + 6 * rowlength));
// replace diagonal product by zero or pre-existing result value
s0 = _mm256_blend_ps(s0, res0, 1 << 0);
s1 = _mm256_blend_ps(s1, res0, 1 << 1);
s2 = _mm256_blend_ps(s2, res0, 1 << 2);
s3 = _mm256_blend_ps(s3, res0, 1 << 3);
s4 = _mm256_blend_ps(s4, res0, 1 << 4);
s5 = _mm256_blend_ps(s5, res0, 1 << 5);
s6 = _mm256_blend_ps(s6, res0, 1 << 6);
s7 = _mm256_blend_ps(s7, res0, 1 << 7);
}
// add remaining elements
for (int col = 0; col < rowlength; col += 8) {
// skip diagonal block. Maybe it is worth splitting the loop into two halves
if (col == row) continue;
__m256 vec_r = _mm256_loadu_ps(f_vecStart + col);
s0 = _mm256_fmadd_ps(vec_r, _mm256_loadu_ps(blockStart + col + 0 * rowlength), s0);
s1 = _mm256_fmadd_ps(vec_r, _mm256_loadu_ps(blockStart + col + 1 * rowlength), s1);
s2 = _mm256_fmadd_ps(vec_r, _mm256_loadu_ps(blockStart + col + 2 * rowlength), s2);
s3 = _mm256_fmadd_ps(vec_r, _mm256_loadu_ps(blockStart + col + 3 * rowlength), s3);
s4 = _mm256_fmadd_ps(vec_r, _mm256_loadu_ps(blockStart + col + 4 * rowlength), s4);
s5 = _mm256_fmadd_ps(vec_r, _mm256_loadu_ps(blockStart + col + 5 * rowlength), s5);
s6 = _mm256_fmadd_ps(vec_r, _mm256_loadu_ps(blockStart + col + 6 * rowlength), s6);
s7 = _mm256_fmadd_ps(vec_r, _mm256_loadu_ps(blockStart + col + 7 * rowlength), s7);
}
// reduce s0-s7 horizontally and store
{
// Perhaps on Piledriver doing vshufps+blend is more efficient?
__m256 s01 = _mm256_hadd_ps(s0, s1);
__m256 s23 = _mm256_hadd_ps(s2, s3);
__m256 s45 = _mm256_hadd_ps(s4, s5);
__m256 s67 = _mm256_hadd_ps(s6, s7);
__m256 s0123 = _mm256_hadd_ps(s01, s23);
__m256 s4567 = _mm256_hadd_ps(s45, s67);
// inter-lane reduction
// combine upper half of s0123 with lower half of s4567:
__m256 res = _mm256_permute2f128_ps(s0123, s4567, 0x21);
// blend lower half of s0123 with upper half of s4567 and add:
res = _mm256_add_ps(res, _mm256_blend_ps(s0123, s4567, 0xF0));
// store result. Ideally, replace by store_ps, if you can guarantee 32byte alignment
_mm256_storeu_ps(result + row, res);
}
}
}