Как хранить значения в несмежных местах памяти с SSE Intrinsics? - PullRequest
5 голосов
/ 19 октября 2010

Я очень новичок в SSE и оптимизировал часть кода с использованием встроенных функций.Я доволен самой операцией, но я ищу лучший способ написать результат.Результаты заканчиваются в трех _m128i переменных.

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

__m128i values0,values1,values2;

/*Do stuff and store the results in values0, values1, and values2*/

y[0]        = (BYTE)_mm_extract_epi16(values0,0);
cb[2]=cb[3] = (BYTE)_mm_extract_epi16(values0,2);
y[3]        = (BYTE)_mm_extract_epi16(values0,4);
cr[4]=cr[5] = (BYTE)_mm_extract_epi16(values0,6);

cb[0]=cb[1] = (BYTE)_mm_extract_epi16(values1,0);
y[1]        = (BYTE)_mm_extract_epi16(values1,2);
cr[2]=cr[3] = (BYTE)_mm_extract_epi16(values1,4);
y[4]        = (BYTE)_mm_extract_epi16(values1,6);

cr[0]=cr[1] = (BYTE)_mm_extract_epi16(values2,0);
y[2]        = (BYTE)_mm_extract_epi16(values2,2);
cb[4]=cb[5] = (BYTE)_mm_extract_epi16(values2,4);
y[5]        = (BYTE)_mm_extract_epi16(values2,6);

Где y, cb и cr - это байтовые (unsigned char) массивы.Это кажется мне неправильным по причинам, которые я не могу определить.У кого-нибудь есть предложения по улучшению?

Спасибо!

Ответы [ 4 ]

9 голосов
/ 20 октября 2010

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

Если этого не сделать, PEXTRW op (_mm_extract_epi16 intrinsic) является практически единственным способом извлечь короткое замыкание из регистра SSE и сохранить его в целочисленном регистре. Другой доступный вам подход заключается в использовании операций распаковки и перемешивания (_mm_shuffle_ps и т. Д.) Для поворота данных в младшее слово регистра и затем MOVSS / _mm_store_ss() для сохранения этого младшего слова в памяти по одному .

Вы, вероятно, обнаружите, что использование объединения или перемещения данных между регистрами SSE и общего назначения обеспечит очень низкую производительность из-за тонкой детализации ЦП, называемой load - hit - магазин ларек. По сути, нет прямого способа перемещать данные между типами регистров; процессор должен сначала записать данные SSE в память, а затем снова прочитать их в GPR. Во многих случаях это означает, что он должен остановить операцию загрузки и подождать, пока хранилище не очистится, прежде чем можно будет выполнить какие-либо дальнейшие инструкции.

2 голосов
/ 20 октября 2010

SSE не обладает необходимой вам функцией разброса / сбора, хотя это, вероятно, появится в будущих архитектурах SIMD.

Как уже было предложено, вы можете использовать объединение, например:

typedef union
{
    __m128i v;
    uint8_t a8[16];
    uint16_t a16[8];
    uint32_t a32[4];
} U128;

В идеале такого рода манипуляции происходят только вне каких-либо критических циклов, поскольку они очень неэффективны по сравнению с простыми операциями SIMD над смежными элементами данных.

2 голосов
/ 19 октября 2010

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

0 голосов
/ 19 октября 2010

Вы можете попробовать использовать union для извлечения байтов.

union
{
    float value;
    unsigned char ch[8];
};

, а затем присваивайте байты по мере необходимости.
Поиграйте с union-idea, возможно, замените unsigned char ch [8] анонимной структурой?
Может быть, вы можете получить больше идей от здесь

...