· Начало · Отвђтить · Статистика · Поиск · FAQ · Правила · Установки · Язык · Выход · WASM.RU · Noir.Ru ·

 WASM Phorum —› WASM.ASSEMBLER —› bswap на C/C++

Посл.отвђт Сообщенiе


Дата: Май 5, 2004 00:37:53

Собсно сабж, как бы замутит побыстрее, и без чтения/записи в памятьть, т.е. арифметическими операциями над 32х битным числом.


Дата: Май 5, 2004 04:21:04

#pragma warning(disable:4035)
__inline int _bswap(int val){
 _asm{
  mov eax,val
  mov edx,eax
  shr eax,16
  rol ax,8
  rol dx,8
  shl edx,16
  or eax,edx
 }
}


Дата: Май 5, 2004 09:28:43 · Поправил: S_T_A_S_

Хм.. а чем собсно BSWAP хуже этого ^^

ЗЫ
Что-то мне кажется где-то есть какой-то подвох :)


----------------
похоже понял.. надо вариант Quantum перевести на С =)


Дата: Май 5, 2004 13:32:40 · Поправил: Dr.Golova

> похоже понял.. надо вариант Quantum перевести на С

Именно, знаете, не на всех серверах стоят x86 совместимые камни :)
А на асме извратиться я и сам могу:
xchg  al, ah
rol   eax, 10h
xchg  al, ah


Дата: Май 5, 2004 14:08:21

Я тут подумал, и решил написать маленький тест.
#include <windows.h>

/*__inline*/ DWORD _bswap1(DWORD val)
{
	return LOBYTE(LOWORD(val))<<24
		 | HIBYTE(LOWORD(val))<<16
		 | LOBYTE(HIWORD(val))<<8
		 | HIBYTE(HIWORD(val));
}

#pragma warning(disable:4035)
/*__inline*/ DWORD _bswap2(DWORD val)
{
	__asm
	{
		mov eax,val
		bswap eax
	}
}
#pragma warning(default:4035)

/*__inline*/ DWORD _bswap3(DWORD val)
{
	return ((val&0xFF)<<24)
		 | ((val&0xFF00)<<8)
		 | ((val&0xFF0000)>>8)
		 | ((val&0xFF000000)>>24);
}

#pragma warning(disable:4035)
/*__inline*/ DWORD _bswap4(DWORD val)
{
	__asm
	{
		mov eax,val
		rol ax,8
		rol eax,16
		rol ax,8
	}
}
#pragma warning(default:4035)

#pragma warning(disable:4035)
/*__inline*/ DWORD _bswap5(DWORD val){
 _asm{
  mov eax,val
  mov edx,eax
  shr eax,16
  rol ax,8
  rol dx,8
  shl edx,16
  or eax,edx
 }
}
#pragma warning(default:4035)

DWORD a=0x12345678;

#pragma comment(linker, "/entry:main")
void main()
{
	char buf1[1024];
	char buf2[1024];
	DWORD b1,b2,b3,b4,b5;
	DWORD t1,t2,t3,t4,t5,t6;
	DWORD i;
	DWORD loops=0x10000000;

	t1=GetTickCount();
	for (i=0;i<loops;i++) b1=_bswap1(a);
	t2=GetTickCount();
	for (i=0;i<loops;i++) b2=_bswap2(a);
	t3=GetTickCount();
	for (i=0;i<loops;i++) b3=_bswap3(a);
	t4=GetTickCount();
	for (i=0;i<loops;i++) b4=_bswap4(a);
	t5=GetTickCount();
	for (i=0;i<loops;i++) b5=_bswap5(a);
	t6=GetTickCount();
	wsprintf(buf1,"0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X",b1,b2,b3,b4,b5);
	wsprintf(buf2,"swap1=%i, swap2=%i, swap3=%i, swap4=%i, swap5=%i",t2-t1,t3-t2,t4-t3,t5-t4,t6-t5);
	MessageBox(0,buf2,buf1,0);
	ExitProcess(0);
}
Инлайны специально закомментировал, чтобы оптимизатор не повыбрасывал циклы нафиг :)
И вот результат на селероне 2600 (запускал 3 раза, просто на всякий случай):
swap1=1943, swap2=1292, swap3=1512, swap4=1512, swap5=1512
swap1=1942, swap2=1282, swap3=1522, swap4=1503, swap5=1522
swap1=1953, swap2=1282, swap3=1512, swap4=1522, swap5=1522
Однако, похоже, что собственно bswap и есть быстрее всего :)


Дата: Май 5, 2004 14:15:32 · Поправил: RobinFood

Пока писал, появилось уточнение требований :)
Тогда вставлю еще 5 копеек вдогонку.

Если камень не x86, и именно на нем надо добиться оптимальной скорости, то лучше потестировать разные варианты конкретно на этом камне (и, кстати, конкретно на используемом компиляторе). Ну а если все-таки хочется кросс-платформенности, то, думаю, вполне можно брать вариант №3 - он по скорости от вариантов 4 и 5 практически не отличается (отличие в пределах погрешности измерений).


Дата: Май 5, 2004 22:41:20

Dr.Golova
И не все процессоры поддерживают xchg ah,al. Лучше уж rol ax,8, хотя и это не универсально. Я тут подумал немного и решил, что нормально реализовать bswap можно только через ror/rol (т.е. без ротации регистра никак не выйдет), но C/C++ не поддерживает такой оператор (Вы и сами, наверное, уже заметили :-) А что мешает написать ассемблерные вставки под каждую архитектуру? Ну, вроде

#ifdef _486
__inline int _bswap(int val){
_asm{
mov eax,val
bswap eax
}
}
#endif

#ifdef _386
__inline int _bswap(int val){
_asm{
mov eax,val
xchg al,ah
rol eax,10h
xchg al,ah
}
}
#endif

#ifdef _big_endian_proc
__inline int _bswap(int val){
// А тут может вообще не надо переворачивать,
// усли оно в памяти лежит в big endian
}
#endif

и т.д.


Дата: Июн 9, 2004 11:12:57

Dr.Golova
Если требуется это сделать на VS - то можно воспользоваться _byteswap_ulong IMHO самый быстрый метод без асма на VS C++. Вставляет инструкцию bswap.


Дата: Июн 10, 2004 10:44:31 · Поправил: S_T_A_S_

> И вот результат на селероне 2600 (запускал 3 раза, просто на всякий случай):
swap1=1943, swap2=1292, swap3=1512, swap4=1512, swap5=1512

imho - тестирование не корректно проводилось.
Резон - BSWAP выполняется один такт, а остальные варианты явно больше.


Вариант без ROL/ROR и с низкой зависимостью по данным.
mov	eax, [arg]

mov     ecx, 00000ff00h
mov	edx, 000ff0000h
mov	ebx, 0ff000000h
and	ecx, eax
and	edx, eax
and	ebx, eax
and	eax, 0000000ffh
shl	ecx, 08h
shr	edx, 08h
shr	ebx, 18h
shl	eax, 18h
or	ecx, edx
or	eax, ebx
or	eax, ecx

Возможно, так бы мог и MSVC делать для (arg&0xFF)<<24|(arg&0xFF00)<<8|(arg&0xFF0000)>>8|(arg&0xFF000000)>>24
но он почему-то делает по-другому :)


Дата: Июн 10, 2004 14:19:42

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

Кстати, можешь выполнить этот же тест (+ твой вариант) на своем компе и дать результаты?

P.S. А почему BSWAP выполняется 1 такт? И вообще, где можно посмотреть "растактовку" команд? В helppc когда-то были расписаны времена выполнения команд, но в описании IA-32 ничего подобного нету...


Дата: Июн 10, 2004 19:51:55

Дык.. тут с тестированием сложно.
Обвязка (цикл) весит сравнимо со сравниваемым кодом, так что даже если юзать RDTSС, то результат будет слишком не точный.
А GetTickCount() - не уверен что лучше.
Да самое гдавное - там же CALL / RET ещё происходит к тестируемым ф-циям.
IMHO надо вставлять варианты BSWAP в реальный код и смотреть уже его.. а так тест черезчур синтетический.


Поэтому результаты у меня такие (AthlonXP2000+):
от
swap1=3938, swap2=1984, swap3=3203, swap4=2016, swap5=2359
до
swap1=829, swap2=484, swap3=0, swap4=500, swap5=703
Зависят от ключей компиляции :).


По поводу BSWAP - сорри, не написал, что это для Athlon так.
Для ядра P6 пишут, что BSWAP весит 2 mops, не спаривается. На PIV совсем туго - latency = 7.

В новых доках intel на некоторые инструкции информацию убрал (возможно, чтобы не было видно заметного отставания от P6).
Но есть старые, например Order Number: 730795-001, или совсем Order Number 242816-003).
Наверняка где-нибудь на intel.com до сих пор лежат :)
Для AMD - 22007.pdf


Дата: Июн 10, 2004 22:56:53

S_T_A_S_
По поводу BSWAP - сорри, не написал, что это для Athlon так.
Для ядра P6 пишут, что BSWAP весит 2 mops, не спаривается. На PIV совсем туго - latency = 7.

А почему ты умолчал что Throughput для bswap = 1
В реальных прогах все равно затраты на 1 такт получается.


Дата: Июн 10, 2004 23:20:07 · Поправил: S_T_A_S_

Latency: The number of clock cycles that are required for the execution core to complete the execution of all of the µops that form a IA-32 instruction.

Throughput: The number of clock cycles to wait before the issue ports are free to accept the same instruction again.


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

Простой пример:
	mov	eax, [foo]
	bswap	eax
	mov	ecx, [eax] 


Powered by miniBB 1.6 © 2001-2002
Время загрузки страницы (сек.): 0.082