О векторном умножении: нет гигапикселя в секунду
lexa - 13/Сен/2011 15:29
Тема матричного цветопреобразования не отпускает нас.
Наш читатель 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);
}
}
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);
}
}
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 и
Понятно. Спасибо.
Про 8 и 4.
Значит при переносе в блог так поправилось. Я себе copy/paste сделал в надежде посмотреть на компе, но не добрался с AVX. Тогда еще и удивился про объявленный и не используемый zero, необъявленный и неинициализированный ymm15 и про
i=0;i. Подумал кусок кода просто для иллюстрации идеи и команды asm-овые мне незнакомые, надобы повнимательнее на досуге посмотреть. И заодно на нумерацию строк поругался. Погоглил, заглянул а как это у других делается. А тут и новая версия появилась. Про 4 раза наверное и не стал бы писать. Но это мелочи.
Тшорт. Оно кушает. Ну значит
Тшорт. Оно кушает. Ну значит ненужное было. :-)
<code> лечит нас.Может и в
<code> лечит нас.
Может и в 8, сейчас уже не вспомню, а в vcs это не клал.
А ошибки я правлю, когда посты перечитываю. Ошибки бывают, потому что в блог попадает концепт, а не реальный код (а в реальном коде может быть десяток #ifdef для разного префетча - если я его тестирую).