О векторном умножении: нет гигапикселя в секунду

Тема матричного цветопреобразования не отпускает нас.

Наш читатель maratyszcza намекает нам, что haddps - тоже хорошая инструкция.

Его вариант вы найдете по вышеуказанной ссылке, а я потестировал вариант попроще, без unroll, без префетча, без одновременной обработки половинок данных. Исключительно для совместимости с прошлыми тестами:

ALIGN1(32) float _m_zero[8] ALIGN2(32) = {0.f,0.f,0.f,0.f, 0.f,0.f,0.f,0.f};
void perm256(float data[], int sz)
{
        int i;
        __m256 m0 = _mm256_load_ps(matX2[0]);
        __m256 m1 = _mm256_load_ps(matX2[1]);
        __m256 m2 = _mm256_load_ps(matX2[2]);
        __m256 zero = _mm256_load_ps(_m_zero);

        for(i=0;i<sz/2;i++)
        {
                __m256 ymm2 = _mm256_load_ps(&data[i*8]);
                __m256 ymm0 = _mm256_mul_ps(ymm2,m0);
                __m256 ymm1 = _mm256_mul_ps(ymm2,m1);
                ymm2 = _mm256_mul_ps(ymm2,m2);
                ymm0 = _mm256_hadd_ps(ymm0,ymm1);
                ymm2 = _mm256_hadd_ps(ymm2,zero);
                ymm0 = _mm256_hadd_ps(ymm0,ymm2);
                _mm256_store_ps(&data[i*8],ymm0);
        }
}
Три dpps и два blendps меняем на три умножения и три горизонтальных сложения результат завораживает:
  • 1031 Mpix/sec с интеловским компилятором, который, отчего-то не грузит значение в регистр, а прямо ymmword ptr лепит в mulps, но зато переупорядочивает инструкции.
  • 1063 Mpix/sec с Visual C++, который делает все прямо как написано.

Update: к сожалению, я при тестировании ошибся в инварианте цикла и реальность не такая великолепная: 725 Mpix/sec. Да, быстрее, чем все что было раньше, но не в полтора раза, как показалось изначально.

Comments

Ололо, марат!

Вот смотрю и не могу понять. Количество проходов цикла увеличилось в 8 раз (i+=8 супротив i++), а скорость уменьшилась ~ в 1.5 раза. Получается в предыдущем варианте уперлись во что-то (память/данные не в кэше)? И для данной реализации более 2-х потоков не имеет смысла? Или там неинициализированный ymm15 покопался?

Естественно, это утыкается в cache misses. Если мы идем с шагом 256 байт, то все (относительно) плохо, L1 страдает дважды: и от misses и в ассоциативность ему больно бьют постоянно.

Только не в 8, а в 4, у меня было меньше sz и +=8, а стало sz/2 и ++.

Что же до числа потоков, то много я не экспериментировал, а банальное #pragma omp parallel for
(и даже если сделать большими кусками и на куски звать эту подпрограмму) - дает очень мало. Ну может процентов 20.

ymm15 не копался - в программе у меня все называется регистрами, а при переносе в блог я имена переменных сделал покрасивее, а это место - изначально забыл.

Так то у меня оно даже проверяется после прогона, все ли правильно (первые 128 значений, конечно).

Понятно. Спасибо.
Про 8 и 4.
Значит при переносе в блог так поправилось. Я себе copy/paste сделал в надежде посмотреть на компе, но не добрался с AVX. Тогда еще и удивился про объявленный и не используемый zero, необъявленный и неинициализированный ymm15 и про i=0;i. Подумал кусок кода просто для иллюстрации идеи и команды asm-овые мне незнакомые, надобы повнимательнее на досуге посмотреть. И заодно на нумерацию строк поругался. Погоглил, заглянул а как это у других делается. А тут и новая версия появилась. Про 4 раза наверное и не стал бы писать. Но это мелочи.

Тшорт. Оно кушает. Ну значит ненужное было. :-)

<code> лечит нас.

Может и в 8, сейчас уже не вспомню, а в vcs это не клал.

А ошибки я правлю, когда посты перечитываю. Ошибки бывают, потому что в блог попадает концепт, а не реальный код (а в реальном коде может быть десяток #ifdef для разного префетча - если я его тестирую).