Перемещение одного поплавка в регистр xmm

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

введите описание изображения здесь

Как видите, у меня есть регистр xmm0 с моими данными. Например, он содержит:

xmm0 = | 4.0 | 2.5 | 3.5 | 2.0 |

Каждая плавающая точка хранится в 4 байтах. У меня регистр xmm0 128 бит, длина 16 байт.

Это неплохо работает. Теперь я хочу сохранить 0,5 в другом регистре xmm, например. xmm1, и умножьте этот регистр на регистр xmm0, чтобы каждое значение, хранящееся в xmm0, умножалось на 0,5.

Я совершенно не знаю, как хранить 0,5 в регистре XMM. Какие-либо предложения?

Кстати: это встроенный ассемблер на C ++.

void filter(image* src_image, image* dst_image)
{
    float* src = src_image->data;
    float* dst = dst_image->data;

    __asm__ __volatile__ (              
        "movaps (%%esi), %%xmm0\n"      
        // Multiply %xmm0 with a float, e.g. 0.5
        "movaps %%xmm0, (%%edi)\n" 

        :
        : "S"(src), "D"(dst) :  
    );
}

Это тихая простая версия того, чем я хочу заниматься. У меня есть данные изображения, хранящиеся в массиве с плавающей запятой. Указатель на эти массивы передается в сборку. movaps принимает первые 4 значения массива с плавающей запятой, сохраняет эти 16 байтов в регистре xmm0. После этого xmm0 следует умножить на, например, 0,5. Затем "новые" значения должны быть сохранены в массиве из edi.


person Basic Coder    schedule 27.11.2011    source источник
comment
Сейчас лучше использовать встроенные функции. Таким образом, ваш код не зависит от компилятора, и вы получаете автоматическое распределение регистров.   -  person Axel Gneiting    schedule 27.11.2011


Ответы (5)


Как отмечали люди в комментариях, для такого рода очень простых операций по сути всегда лучше использовать встроенные функции:

void filter(image* src_image, image* dst_image)
{
    const __m128 data = _mm_load_ps(src_image->data);
    const __m128 scaled = _mm_mul_ps(data, _mm_set1_ps(0.5f));
    _mm_store_ps(dst_image->data, scaled);
}

Вы должны прибегать к встроенному ASM только в том случае, если компилятор генерирует неправильный код (и только после регистрации ошибки с поставщиком компилятора).

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

    const __m128 half = _mm_set1_ps(0.5f);

а затем используйте его внутри ASM, как и другие операнды.

Вы можете сделать это без каких-либо нагрузок, если очень хотите:

    "mov    $0x3f000000, %%eax\n"  // encoding of 0.5
    "movd   %%eax,       %%xmm1\n" // move to xmm1
    "shufps $0, %%xmm1,  %%xmm1\n" // splat across all lanes of xmm1

Это всего лишь два подхода. Есть много других способов. Вы можете провести некоторое время с пользой для себя со Справочником по набору инструкций Intel.

person Stephen Canon    schedule 27.11.2011
comment
+1 MOVD с немедленным значением намного лучше моей версии, которая может загружаться только из памяти. Не думал об использовании целочисленных операций. - person Christian Rau; 27.11.2011
comment
@ChristianRau: Я не думаю, что сказал бы, что это намного лучше; это будет зависеть от окружающего контекста. Это просто разные подходы. - person Stephen Canon; 27.11.2011
comment
Я думаю, что это может быть лучше для константы, поскольку она поступает непосредственно из кеша инструкций, и вам не нужно извлекать ее из статической памяти. Но вы правы в том, что все зависит от обстоятельств, и я не слишком разбираюсь в машинах. - person Christian Rau; 27.11.2011
comment
@Copa: Я просто знаю это значение; Я пишу много низкоуровневого кода FP. Число с одинарной точностью IEEE-754 имеет 8-битное поле экспоненты и 23-битное поле значащей. Смещение поля экспоненты составляет 127. Итак, 1.0f = 2^0 равно 127 + 0 << 23 или 0x3f800000; 0.5f = 2^-1 - это 127 - 1 << 23, то есть 0x3f000000. Простые смертные могут предпочесть использовать babbage.cs.qc.edu/IEEE-754. = P - person Stephen Canon; 27.11.2011
comment
@Christian: загрузка регистра xmm из регистра общего назначения может повлечь за собой штраф, который намного больше, чем задержка кэша L1, в зависимости от конкретного оборудования. Кроме того, любая задержка может быть скрыта путем изменения порядка. - person Gunther Piez; 28.11.2011
comment
@drhirsch: movd был однократным на каждом µarch, начиная с Core. Я не думаю, что на данный момент существует много высокопроизводительного кода, ориентированного на Netburst. Тем не менее, ваша точка зрения о переупорядочении точна, и это более или менее то, что я имел в виду. - person Stephen Canon; 28.11.2011
comment
Я больше имел в виду задержки обхода данных, присутствующие даже в архитектуре Core i (Nehalem), которая не такая уж и старая. В pdf-формате микроархитектуры Agner Fogs, о котором, я уверен, вы знаете, он указан как 1 дополнительный цикл (в разделе «Порты выполнения»), и, конечно же, это еще одна инструкция (сразу mov + movd против mov mem, xmm), которая может засорить. декодеры инструкций. Иногда я действительно считаю каждый цикл в некоторых местах и ​​обнаружил, что даже в современной архитектуре mov mem, xmm может быть предпочтительнее (в других случаях нет :-)). Но вы, конечно, правы, это всегда зависит от человека. контекст. - person Gunther Piez; 28.11.2011
comment
@drhirsch: Есть ровно три сценария, о которых я знаю, в которых создание значения FP таким образом является чистой победой: (1) если вы полностью привязаны к задержке (из-за цепочки зависимостей, поэтому долго, что буфер переупорядочения заполняется и ничего вам не покупает) - даже с задержкой обхода Core i * задержка меньше (2), если вы используете 32-разрядную версию и вам нужно загрузить одну или две константы, это избавляет от необходимости отказываться от регистра для базового адреса PIC (и сохраняет две инструкции для генерации указанного адреса) (3), если ваш рабочий набор точно заполняет L1 D $, и это предотвращает промах. Все это угловые случаи. - person Stephen Canon; 28.11.2011

Предполагая, что вы используете встроенные функции: __m128 halfx4 = _mm_set1_ps(0.5f);

Изменить:

Намного лучше использовать встроенные функции:

__m128 x = _mm_mul_ps(_mm_load_ps(src), halfx4);
_mm_store_ps(dst, x);

Если данные с плавающей запятой src и dst не выровнены по 16 байтам, вам потребуются: _mm_loadu_ps и _mm_storeu_ps, которые работают медленнее.

person Brett Hale    schedule 27.11.2011
comment
Могу ли я использовать это во встроенном ассемблере? - person Basic Coder; 27.11.2011
comment
Вы можете передать его как операнд. Какой компилятор вы используете? - person Brett Hale; 27.11.2011
comment
Я использую gcc для linux. Что вы имеете в виду, передавая его как операнд? - person Basic Coder; 27.11.2011
comment
Вам нужно показать встроенный блок сборки - в частности, его входы и выходы. - person Brett Hale; 27.11.2011

Вам нужна инструкция MOVSS (которая загружает из памяти число с плавающей запятой одинарной точности в младшие 4 байта регистра SSE), за которой следует тасование, чтобы заполнить остальные 3 числа с плавающей запятой этим значением:

movss  (whatever), %%xmm1
shufps %%xmm1, %%xmm1, $0

Вероятно, именно так могла бы сделать _mm_set1_ps внутренняя функция. Затем вы можете просто умножить эти значения SSE или сделать что хотите:

mulps %%xmm1, %%xmm0
person Christian Rau    schedule 27.11.2011

Если вы используете c ++ с gcc и у вас есть EasySSE, ваш код может быть следующим

void filter(float* src_image, float* dst_image){
    *(PackedFloat128*)dst_image =  Packefloat128(0.5) * (src_image+0);
}

Предполагается, что указанные указатели выровнены по 16 байт. Вы можете проверить код сборки, чтобы убедиться, что переменные правильно сопоставлены с векторными регистрами.

person Ogan Ocali    schedule 29.11.2011

Вот один из способов сделать это:

#include <stdio.h>
#include <stdlib.h>

typedef struct img {
    float *data;
} image_t;

image_t *src_image;
image_t *dst_image;
void filter(image_t*, image_t*);

int main()
{
    image_t src, dst;
    src.data = malloc(64);
    dst.data = malloc(64);
    src_image=&src;
    dst_image=&dst;

    *src.data = 42.0;
    filter(src_image, dst_image);

    printf("%f\n", *dst.data);
    free(src.data);
    free(dst.data);
    return 0;
}

void filter(image_t* src_image, image_t* dst_image)
{
    float* src = src_image->data;
    float* dst = dst_image->data;

    __asm__ __volatile__ (              
        "movd   %%esi, %%xmm0;"
        "movd   %%xmm0, %%edi;"
        : "=D" (*dst)
        : "S" (*src)
    );
}
person Brian    schedule 04.07.2017