Загрузка констант с плавающей точкой в ​​регистры SSE - PullRequest
11 голосов
/ 15 февраля 2011

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

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

но это генерирует 4 инструкции movss из памяти!

movss       xmm0,dword ptr [__real@3f800000 (14048E534h)] 
movss       xmm1,dword ptr [__real@40000000 (14048E530h)] 
movaps      xmm6,xmm12 
shufps      xmm6,xmm12,0C6h 
movss       dword ptr [rsp],xmm0 
movss       xmm0,dword ptr [__real@40400000 (14048E52Ch)] 
movss       dword ptr [rsp+4],xmm1 
movss       xmm1,dword ptr [__real@40a00000 (14048E528h)] 

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

Делая это, хотя ..

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

генерирует.

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

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

Спасибо

Ответы [ 4 ]

6 голосов
/ 16 февраля 2011

Если вы хотите принудить его к одной загрузке, вы можете попробовать (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 C ++, используйте __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)) может, в зависимости от вашего компилятора, создать неправильный код, который этого не делает, поэтому будьте осторожны и проверьте разборку.

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

    movaps  vec.7330, %xmm0
    ...
vec.7330:
    .long   1065353216
    .long   1066192077
    .long   1067030938
    .long   1067869798
3 голосов
/ 15 февраля 2011

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

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

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

2 голосов
/ 17 февраля 2011

Генерировать константы намного проще (и быстрее), если четыре константы с плавающей точкой одинаковы. Например, битовая комбинация для 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, я ее не профилировал, только проверял, что результат был верным.

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

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

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

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

2 голосов
/ 15 февраля 2011

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

...