Постоянная нагрузки плавает в регистры SSE - программирование

Постоянная нагрузки плавает в регистры SSE

Я пытаюсь найти эффективный способ загрузки поплавков постоянной времени компиляции в регистры SSE (2/3). Я пробовал делать простой код, например,

const __m128 x = { 1.0f, 2.0f, 3.0f, 4.0f }; 

но который генерирует 4 команды movss из памяти!

movss       xmm0,dword ptr [[email protected] (14048E534h)] 
movss       xmm1,dword ptr [[email protected] (14048E530h)] 
movaps      xmm6,xmm12 
shufps      xmm6,xmm12,0C6h 
movss       dword ptr [rsp],xmm0 
movss       xmm0,dword ptr [[email protected] (14048E52Ch)] 
movss       dword ptr [rsp+4],xmm1 
movss       xmm1,dword ptr [[email protected] (14048E528h)] 

которые загружают скаляры в и из памяти... (?!?!)

Выполнение этого, хотя..

float Align(16) myfloat4[4] = { 1.0f, 2.0f, 3.0f, 4.0f, }; // out in global scope

генерирует.

movaps      xmm5,xmmword ptr [::myarray4 (140512050h)]

В идеале было бы неплохо, если бы у меня были константы, то это было бы способом не касаться памяти и просто делать это с помощью непосредственных инструкций стиля (например, константы, скомпилированные в самой инструкции).

Спасибо

4b9b3361

Ответ 1

Если вы хотите принудительно установить его на одну нагрузку, вы можете попробовать (gcc):

__attribute__((aligned(16))) float vec[4] = { 1.0f, 1.1f, 1.2f, 1.3f };
__m128 v = _mm_load_ps(vec); // edit by sor: removed the "&" cause its already an address

Если у вас есть Visual С++, используйте __declspec(align(16)) для запроса правильного ограничения.

В моей системе это (скомпилировано с gcc -m32 -msse -O2; отсутствие оптимизации вообще загромождает код, но в конце концов сохраняет единственный movaps), создает следующий код сборки (синтаксис gcc/AT & T):

    andl    $-16, %esp
    subl    $16, %esp
    movl    $0x3f800000, (%esp)
    movl    $0x3f8ccccd, 4(%esp)
    movl    $0x3f99999a, 8(%esp)
    movl    $0x3fa66666, 12(%esp)
    movaps  (%esp), %xmm0

Обратите внимание, что он выравнивает указатель стека перед распределением стекового пространства и помещением констант в него. Выход из __attribute__((aligned)) out может, в зависимости от вашего компилятора, создать неправильный код, который этого не сделает, поэтому будьте осторожны и проверьте разборку.

Дополнительно:
Поскольку вы запрашиваете, как вставлять константы в код, просто попробуйте выше с помощью static квалификатора для массива float. Это создает следующую сборку:

    movaps  vec.7330, %xmm0
    ...
vec.7330:
    .long   1065353216
    .long   1066192077
    .long   1067030938
    .long   1067869798

Ответ 2

Во-первых, какой уровень оптимизации вы компилируете? Это не редкость видеть, что вид codegen на -O0 или -O1, но я был бы очень удивлен, увидев его с -O2 или выше в большинстве компиляторов.

Во-вторых, в SSE нет непосредственных нагрузок. Вы можете немедленно загрузить GPR, а затем перенести это значение в SSE, но не можете вызвать другие значения без фактической нагрузки (игнорируя некоторые специальные значения, такие как 0 или (int)-1, которые могут быть созданы с помощью логических операций.

Наконец, если генерируется плохой код с включенными оптимизациями и в критическом для производительности месте, пожалуйста, сообщите об ошибке в отношении вашего компилятора.

Ответ 3

Обычно такие константы будут загружаться до любых циклов или "горячих" частей кода, поэтому производительность не должна быть такой важной. Но если вы не можете избежать такого рода вещей внутри цикла, я сначала попробую _mm_set_ps и посмотрю, что это генерирует. Также попробуйте ICC, а не gcc, поскольку он имеет тенденцию генерировать лучший код.

Ответ 4

Генерирующие константы намного проще (и быстрее), если четыре константы поплавка одинаковы. Например, битовая диаграмма для 1.f равна 0x3f800000. Один из способов можно создать с помощью SSE2

        register __m128i onef;
        __asm__ ( "pcmpeqb %0, %0" : "=x" ( onef ) );
        onef = _mm_slli_epi32( onef, 25 );
        onef = _mm_srli_epi32( onef, 2 );

Другой подход с SSE4.1:

        register uint32_t t = 0x3f800000;
        register __m128 onef;
        __asm__ ( "pinsrd %0, %1, 0" : "=x" ( onef ) : "r" ( t ) );
        onef = _mm_shuffle_epi32( onef, 0 );

Обратите внимание, что я не могущественный, если эта версия работает быстрее, чем SSE2, не профилировала ее, проверяла только результат.

Если значения каждого из четырех поплавков должны быть разными, тогда каждая из констант может быть сгенерирована и перетасована или смешана вместе.

Wether или нет, это полезно, зависит от того, вероятна ли ошибка кеша, иначе загрузка константы из памяти происходит быстрее. Хитрости, подобные этому, очень полезны в vmx/altivec, но большие кеши на большинстве ПК могут сделать это менее полезным для sse.

Это хорошее обсуждение этого в Руководстве по оптимизации Agner Fog, книга 2, раздел 13.4, http://www.agner.org/optimize/.

Заключительное примечание: использование встроенного ассемблера выше - спецификация gcc, причина заключается в том, чтобы разрешить использование неинициализированных переменных без генерации предупреждения компилятора. С помощью vc вам может потребоваться или не понадобиться сначала инициализировать переменные с помощью _mm_setzero_ps(), а затем надеяться, что оптимизатор может удалить это.