Операция MMX (добавление 16 бит не выполняется)

У меня есть несколько векторов, содержащих символы без знака, которые представляют пиксели из кадра. У меня эта функция работает без улучшения MMX, но я разочарован тем, что MMX не работает... Итак:

Мне нужно добавить два беззнаковых символа (сумма должна быть 16-битной, а не 8-битной, потому что беззнаковый символ идет от 0-255, как известно) и разделить их на два (сдвиг вправо 1). Код, который я сделал до сих пор, приведен ниже, но значения неверны, add_pu16 не добавляет 16-битный только 8:

  MM0 = _mm_setzero_si64();        //all zeros
  MM1 = TO_M64(lv1+k);             //first 8 unsigned chars
  MM2 = TO_M64(lv2+k);             //second 8 unsigned chars

  MM3 =_mm_unpacklo_pi8(MM0,MM1);  //get first 4chars from MM1 and add Zeros
  MM4 =_mm_unpackhi_pi8(MM0,MM1);  //get last 4chars from MM1 and add Zeros

  MM5 =_mm_unpacklo_pi8(MM0,MM2);  //same as above for line 2
  MM6 =_mm_unpackhi_pi8(MM0,MM2);

  MM1 = _mm_adds_pu16(MM3,MM5);    //add both chars as a 16bit sum (255+255 max range)
  MM2 = _mm_adds_pu16(MM4,MM6);

  MM3 = _mm_srai_pi16(MM1,1);      //right shift (division by 2)
  MM4 = _mm_srai_pi16(MM2,1);

  MM1 = _mm_packs_pi16(MM3,MM4);   //pack the 2 MMX registers into one

  v2 = TO_UCHAR(MM1);              //put results in the destination array

Новые разработки: Спасибо за это king_nak!! Я написал простую версию того, что я пытаюсь сделать:


int main()
{
char A[8]={255,155,2,3,4,5,6,7};
char B[8]={255,155,2,3,4,5,6,7};
char C[8];
char D[8];
char R[8];

__m64* pA=(__m64*) A;

__m64* pB=(__m64*) B;

__m64* pC=(__m64*) C;

__m64* pD=(__m64*) D;

__m64* pR=(__m64*) R;

_mm_empty();

__m64 MM0 = _mm_setzero_si64();

__m64 MM1 = _mm_unpacklo_pi8(*pA,MM0);

__m64 MM2 = _mm_unpackhi_pi8(*pA,MM0);

__m64 MM3 = _mm_unpacklo_pi8(*pB,MM0);

__m64 MM4 = _mm_unpackhi_pi8(*pB,MM0);

__m64 MM5 = _mm_add_pi16(MM1,MM3);

__m64 MM6 = _mm_add_pi16(MM2,MM4);

printf("SUM:\n");

*pC= _mm_add_pi16(MM1,MM3);

*pD= _mm_add_pi16(MM2,MM4);

for(int i=0; i<8; i++) printf("\t%d ", (C[i])); printf("\n");

for(int i=0; i<8; i++) printf("\t%d ", D[i]); printf("\n");

printf("DIV:\n");

*pC= _mm_srai_pi16(MM5,1);

*pD= _mm_srai_pi16(MM6,1);

for(int i=0; i<8; i++) printf("\t%d ", (C[i])); printf("\n");

for(int i=0; i<8; i++) printf("\t%d ", D[i]); printf("\n");

MM1= _mm_srai_pi16(MM5,1);    
MM2= _mm_srai_pi16(MM6,1);

printf("Final Result:\n");
*pR= _mm_packs_pi16(MM1,MM2);
for(int i=0; i<8; i++) printf("\t%d ", (R[i])); printf("\n");

return(0);
}

И результаты таковы:

СУММА:

-2  1   54  1   4   0   6   0 

8   0   10  0   12  0   14  0 

ДЕЛ:

-1  0   -101    0   2   0   3   0 

4   0   5   0   6   0   7   0 

Конечный результат:

127     127     2   3   4   5   6   7 

Ну, маленькие числа в порядке, в то время как большие числа, которые дают 127, неверны. Это проблема, что я делаю не так :с


person Paiva    schedule 29.06.2011    source источник
comment
Не имеет прямого отношения к вашей проблеме, но разве srai не продлевает результат? Хорошо, для добавления двух 8-битных uint вместе потребуется максимум 9 бит, так что здесь это не проблема.   -  person onitake    schedule 29.06.2011
comment
Поскольку вы обрабатываете 16 байтов за раз, почему бы не использовать SSE вместо MMX?   -  person Paul R    schedule 29.06.2011
comment
Потому что требования заключаются в использовании MMX :s С SSE я мог бы сделать за один шаг среднее значение между двумя значениями, и это слишком просто :S   -  person Paiva    schedule 29.06.2011
comment
К сожалению, используемая вами инструкция арифметического сдвига - это SSE2... Так что, если на самом деле требуется только MMX, это не сработает. Если вы можете хотя бы использовать инструкции 3dnow или SSE, вам следует использовать PAVGB для расчета среднего значения между 8 значениями.   -  person onitake    schedule 29.06.2011
comment
Прости, забудь, что я сказал. PSRAW доступен в MMX. Посмотрел не в том месте.   -  person onitake    schedule 29.06.2011


Ответы (3)


Я думаю, что нашел проблему: аргументы инструкций по распаковке расположены в неправильном порядке. Если вы посмотрите на регистры в целом, то покажется, что отдельные символы расширены нулями до коротких, но на самом деле они дополнены нулями. Просто поменяйте местами mm0 и другой регистр в каждом случае, и все должно работать.

Кроме того, вам не нужен насыщенный адд, достаточно обычного PADDW. Максимальное значение, которое вы получите, равно 0xff+0xff=0x01fe, которое не обязательно должно быть насыщенным.

Изменить. Более того, PACKSSWB не совсем то, что вам нужно. PACKUSWB — правильная инструкция, насыщение даст неправильные результаты.

Вот решение (Также заменены сдвиги на логические и местами использованы разные псевдорегистры):

mm0=pxor(mm0,mm0) =[00,00,00,00,00,00,00,00]
mm1 =[a0,10,ff,18,7f,f0,ff,cc]
mm2 =[c0,20,ff,00,70,26,ff,01]
mm3=punpcklbw(mm1,mm0) =[00a0,0010,00ff,0018]
mm4=punpckhbw(mm1,mm0) =[007f,00f0,00ff,00cc]
mm5=punpcklbw(mm2,mm0) =[00c0,0020,00ff,0000]
mm6=punpckhbw(mm2,mm0) =[0070,0026,00ff,0001]
mm5=paddw(mm3,mm5) =[0160,0030,01fe,0018]
mm6=paddw(mm4,mm6) =[00ef,0116,01fe,00cd]
mm3=psrlwi(mm5,1) =[00b0,0018,00ff,000c]
mm4=psrlwi(mm6,1) =[0077,008b,00ff,0066]
mm1=packuswb(mm3,mm4) =[b0,18,ff,0c,77,8b,ff,66]
person onitake    schedule 29.06.2011

Вы должны поменять местами операнды в вызовах _mm_unpacklo_pi8. При этом байты значения находятся в старших байтах слова (например, AB и 00 упакованы в AB00). После сложения и сдвига значения будут больше, чем 0x7F, и, таким образом, будут насыщены до этого значения командой pack.

При переключении операндов математика выполняется для таких значений, как 00AB, и результат помещается в байт со знаком.

ОБНОВЛЕНИЕ:
После вашей дополнительной информации я вижу, что проблема связана с _mm_packs_pi16. Это ассемблерная инструкция packsswb, которая будет насыщать подписанные байты. Например. Значения > 127 будут установлены на 127. (255+255)>>1 равно 255, а (155+155)>>1 равно 155...
Вместо этого используйте _mm_packs_pu16. Это обрабатывает значения как байты без знака, и вы получаете желаемые результаты (255/155).

person king_nak    schedule 29.06.2011
comment
Разве при этом не теряются все средние значения между 0x7f и 0xff? Например, добавьте 0xa0 и 0xc0. Привет-расширенный, это дает 0xa000 и 0xc000 соответственно. 0xa000 + 0xc000 = 0x16000, который будет усечен до 0x6000 (или насыщен до 0xffff, если вы используете насыщенное добавление). Shift, и вы получите 0x30, что не является правильным результатом. - person onitake; 29.06.2011
comment
Я проделал больше работы над этим и написал выше ... до сих пор не знаю, что не так. - person Paiva; 29.06.2011
comment
Я сменил подписанную упаковку на неподписанную, но все равно неправильно :s Результат: -1 -101 2 3 4 5 6 7 - person Paiva; 29.06.2011
comment
Это правильные результаты, так как -1 == 255 и -101 == 155. У вас есть проблема только с отображением знака/без знака и слова/байта. Попробуйте unsigned char x = R[0], это даст вам 255. (также вы можете написать R[i]&0xff в выходном цикле) - person king_nak; 29.06.2011

Кроме того, вам не нужно 16-битное промежуточное значение для вычисления среднего значения двух 8-битных значений. Формулировка:

(a >> 1) + (b >> 1) + (a & b & 1)

дает правильный результат только с необходимыми 8-битными промежуточными звеньями. Возможно, вы можете использовать это для повышения пропускной способности, если у вас есть 8-битные векторные инструкции.

person caf    schedule 30.06.2011
comment
Я также пробовал этот подход, но в наборе инструкций MMX нет инструкций по смещению для 8-битных, только для 16/32/64-битных msdn.microsoft.com/en-us/library/s9fcy11x.aspx - person Paiva; 01.07.2011
comment
@Paiva: обратите внимание, что вы можете эмулировать 8-битные логические сдвиги, маскируя каждый байт & 0xfe перед более широким сдвигом. - person caf; 01.07.2011