Перестановка байтов

Что-то последнее время мне приходится заниматься исключительно переделыванием 16-битных целых чисел из MSB в LSB и назад. То-ли от того это что камера IIDC в непонятном формате отдает кадры, то-ли от того это что FITS в непонятном формате их хранит, но скорее всего просто потому, что компьютеры у меня x86 совместимые.

Итак, стандарт POSIX.1-2001 дает нам отличную для этого функцию - swab, в glibc 2.5 реализована она следующим образом:


void
swab (const void *bfrom, void *bto, ssize_t n)
{
const char *from = (const char *) bfrom;
char *to = (char *) bto;

n &= ~((ssize_t) 1);
while (n > 1)
{
const char b0 = from[--n], b1 = from[--n];
to[n] = b0;
to[n + 1] = b1;
}
}


По моим замерам в моей системе реализация, с эталонным количеством 51.2 мегабайт мусора, справляется в среднем за 110744 миллионных долей секунды.

Но так как просто так было не интересно, а в списке рассылок libdc1394-devel была год назад была обсуждена эта проблема и промелькнуло слово SSE2 ( кстати, новая спецификация IIDC позволит в теории указывать порядок байтов, только где-же таких камер взять нам? ), мой вариант написанный сегодня с использованием SSE2:


void swab_sse2(const void *from, void *to, ssize_t n) {
const char xmm_mask_1[] __attribute__ ((aligned(16))) = {0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff};
const char xmm_mask_2[] __attribute__ ((aligned(16))) = {0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00};

const char *cfrom = (const char *) from;
char *cto = (char *) to;

asm("\
movapd (%0),%%xmm2; \n\
movapd (%1),%%xmm3; \n\
" :
: "r" (xmm_mask_1), "r" (xmm_mask_2)
);

ssize_t i = 0;
while( i < n && ( n - i ) >= 16 ){

asm("\
movupd (%0),%%xmm0; \n\
movapd %%xmm0,%%xmm1; \n\
pslldq $1,%%xmm0; \n\
psrldq $1,%%xmm1; \n\
andpd %%xmm2,%%xmm0; \n\
andpd %%xmm3,%%xmm1; \n\
orpd %%xmm0,%%xmm1; \n\
movupd %%xmm1,(%1); \n\
" :
: "r" (from + i), "r" ( to + i ) );

i += 16;
}

while ( i < n && ( n - i ) >= 2 ){
const char b0 = cfrom[i++], b1 = cfrom[i++];
cto[i - 1] = b0;
cto[i - 2] = b1;
}
}


Компилятору gcc надо скормить -msse2, для компиляции безобразия. Работает в среднем за 98323 миллионных секунды. откуда делаем вывод, что никакого толка в этой затее нет, а происходит некое соревнование меня и оптимизатора gcc :)

p.s. На ассемблере писал первый раз в жизни... и больше не буду )

update: только вчера заметил, что можно обойтись тремя регистрами вместо четырех.

Комментариев нет: