Вместо того, чтобы использовать встроенные средства AVX для использования инструкций SSE

К сожалению, у меня есть процессор AMD piledriver, который, похоже, имеет проблемы с инструкциями AVX:

Запись в память с 256-разрядными регистрами AVX исключительно медленная. Измеренная пропускная способность в 5-6 раз медленнее, чем на предыдущей модели (Bulldozer), и в 8 – 9 раз медленнее, чем две 128-битные записи.

По моему собственному опыту, я нашел mm256 intrinsics намного медленнее, чем mm128, и я предполагаю, что это из-за вышеуказанной причины.

Я действительно хочу кодировать новейший набор команд AVX, хотя, будучи в состоянии тестировать сборки на своей машине с разумной скоростью. Есть ли способ заставить mm256 intrinsics использовать инструкции SSE? Я использую VS 2015.

Если нет простого способа, что с тобой. Замените на пользовательский заголовок, содержащий мои собственные определения для встроенных которые могут быть закодированы для использования SSE? Не уверенный, насколько это правдоподобно, предпочитайте, если возможно, более простой способ, прежде чем я продолжу эту работу.

Используйте Vector Class Library -D__SSE4_2__ -D__XOP__ и добавьте это в командную строку в Visual Studio: -D__SSE4_2__ -D__XOP__ .

Затем используйте вектор размера AVX, такой как Vec8f для восьми поплавков. Когда вы компилируете без возможности AVX, он будет использовать файл vectorf256e.h который эмулирует AVX с двумя SSE-регистрами. Например, Vec8f наследует от Vec256fe который начинается следующим образом:

 class Vec256fe { protected: __m128 y0; // low half __m128 y1; // high half 

Если вы скомпилируете /arch:AVX -D__XOP__ VCL вместо этого будет использовать файл vectorf256.h и один регистр AVX. Затем ваш код работает для AVX и SSE с изменением только смены компилятора.

Если вы не хотите использовать XOP , не используйте -D__XOP__ .


Как указал Питер Кордес в своем ответе, если ваша цель состоит в том, чтобы избежать 256-разрядных нагрузок / хранилищ, вам все же могут понадобиться кодированные инструкции VEX (хотя это не ясно, это будет иметь значение, за исключением некоторых особых случаев). Вы можете сделать это с помощью векторного classа, подобного этому

 Vec8f a; Vec4f lo = a.get_low(); // a is a Vec8f type Vec4f hi = a.get_high(); lo.store(&b[0]); // b is a float array hi.store(&b[4]); 

затем скомпилируйте с /arch:AVX -D__XOP__ .

Другим вариантом будет один исходный файл, который использует Vecnf а затем делает

 //foo.cpp #include "vectorclass.h" #if SIMDWIDTH == 4 typedef Vec4f Vecnf; #else typedef Vec8f Vecnf; #endif 

и скомпилировать

 cl /O2 /DSIMDWIDTH=4 foo.cpp /Fofoo_sse cl /O2 /DSIMDWIDTH=4 /arch:AVX /D__XOP__ foo.cpp /Fofoo_avx128 cl /O2 /DSIMDWIDTH=8 /arch:AVX foo.cpp /Fofoo_avx256 

Это создало бы три исполняемых файла с одним исходным файлом. Вместо того, чтобы связывать их, вы можете просто скомпилировать их с помощью /c и создать диспетчер процессора. Я использовал XOP с avx128, потому что не думаю, что есть веская причина использовать avx128, кроме AMD.

Вы не хотите использовать инструкции SSE. Вы хотите, чтобы хранилища 256b выполнялись как два отдельных хранилища 128b, все еще с инструкциями 128b с VEX-кодированием. т.е. 128b AVX vmovups .


gcc имеет параметры -mavx256-split-unaligned-load и ...-store (включен как часть -march=sandybridge например, предположительно также для семейства Bulldozer ( -march=bdver2 is piledriver). Это не решает проблему проблема, когда компилятор знает, что память выровнена.


Вы можете переопределить нормальный 256b-хранилище, встроенный в макрос, как

 // maybe enable this for all BD family CPUs? #if defined(__bdver2) | defined(PILEDRIVER) | defined(SPLIT_256b_STORES) #define _mm256_storeu_ps(addr, data) do{ \ _mm_storeu_ps( ((float*)(addr)) + 0, _mm256_extractf128_ps((data),0)); \ _mm_storeu_ps( ((float*)(addr)) + 4, _mm256_extractf128_ps((data),1)); \ }while(0) #endif в // maybe enable this for all BD family CPUs? #if defined(__bdver2) | defined(PILEDRIVER) | defined(SPLIT_256b_STORES) #define _mm256_storeu_ps(addr, data) do{ \ _mm_storeu_ps( ((float*)(addr)) + 0, _mm256_extractf128_ps((data),0)); \ _mm_storeu_ps( ((float*)(addr)) + 4, _mm256_extractf128_ps((data),1)); \ }while(0) #endif 

gcc определяет __bdver2 (Bulldozer version 2) для Piledriver ( -march=bdver2 ).

Вы можете сделать то же самое для (aligned) _mm256_store_ps , или просто всегда использовать неприсоединенное внутреннее.

Компиляторы оптимизируют _mm256_extractf128(data,0) для простого _mm256_extractf128(data,0) . Т.е. он должен просто скомпилировать

 vmovups [rdi], xmm0 ; if data is in xmm0 and addr is in rdi vextractf128 [rdi+16], xmm0, 1 

Тем не менее, тестирование на godbolt показывает, что gcc и clang немые , и извлекают в регистр, а затем сохраняют. ICC правильно генерирует последовательность двух команд.