首页
社区
课程
招聘
[求助]求教汇编的问题
发表于: 2008-11-23 10:38 6264

[求助]求教汇编的问题

2008-11-23 10:38
6264


00402F00  |.  8B8424 9C000000   |mov     eax, dword ptr [esp+9C]
00402F07  |.  8B8C24 94000000   |mov     ecx, dword ptr [esp+94]
00402F0E  |.  25 FF000000       |and     eax, 0FF
00402F13  |.  81E1 FF000000     |and     ecx, 0FF
00402F19  |.  8A9441 90DD9900   |mov     dl, byte ptr [ecx+eax*2+99DD90]
00402F20  |.  8A8448 90DD9900   |mov     al, byte ptr [eax+ecx*2+99DD90]

请大家看上面的代码

实际上 [esp+9C]和 [esp+94] 是unsigned char变量,其值肯定不会大于0xFF
而且程序中有大量的连续的这代码段,在已经在以保证eax/acx 里除al/cl 里可能在数据,其它都是0的情况下
VC6.0为什么不把代码编译成

00402F00  |.  8B8424 9C000000   |mov     al, byte ptr [esp+9C]
00402F07  |.  8B8C24 94000000   |mov     cl, byte ptr [esp+94]
00402F19  |.  8A9441 90DD9900   |mov     dl, byte ptr [ecx+eax*2+99DD90]
00402F20  |.  8A8448 90DD9900   |mov     al, byte ptr [eax+ecx*2+99DD90]

这样程序看起会不会更高效呢?

但是我实际用OD来修改成这样后没发现运行速度上的提高.
00402F00      8A8424 9C000000   mov     al, byte ptr [esp+9C]
00402F07      8A8C24 94000000   mov     cl, byte ptr [esp+94]
00402F0E      90                nop
00402F0F      90                nop
00402F10      90                nop
00402F11      90                nop
00402F12      90                nop
00402F13      90                nop
00402F14      90                nop
00402F15      90                nop
00402F16      90                nop
00402F17      90                nop
00402F18      90                nop
00402F19  |.  8A9441 90DD9900   |mov     dl, byte ptr [ecx+eax*2+99DD90]
00402F20  |.  8A8448 90DD9900   |mov     al, byte ptr [eax+ecx*2+99DD90]

NOP是不是也开销CPU的时钟周期呢?

谢谢各位.

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
免费 0
支持
分享
最新回复 (20)
雪    币: 261
活跃值: (22)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
影响程序的效率通常不在这些小处上,而是在算法的选择上。
而对于上面你修改的,我认为会变得慢了....
PC寄存储存下一条指令的地址,增加空指令会加大开销
而读取eax,al的效率应该一样的。
上面都是我的猜测,请大大们继续回答。
2008-11-23 12:57
0
雪    币: 2110
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
3
[QUOTE=盛全;539969]实际上 [esp+9C]和 [esp+94] 是unsigned char变量,其值肯定不会大于0xFF
而且程序中有大量的连续的这代码段,在已经在以保证eax/acx 里除al/cl 里可能在数据,其它都是0的情况下
VC6.0为什么不把代码编译成

00402F00  |.  8B8424 9C000000   |mov     al, byte ptr [esp+9C]
00402F07  |.  8B8C24 94000000   |mov     cl, byte ptr [esp+94]
00402F19  |.  8A9441 90DD9900   |mov     dl, byte ptr [ecx+eax*2+99DD90]
00402F20  |.  8A8448 90DD9900   |mov     al, byte ptr [eax+ecx*2+99DD90]

这样程序看起会不会更高效呢?[/QUOTE]

请问楼主是学习计算机专业的吗?

CPU按总线宽度执行地址对齐的存储器访问是效率最高的,只需要一个标准的总线周期即可完成。至于像and eax,0xff这样的指令,基本上可以认为所需要的时间与CPU主频差相同,只需要一个时钟周期。

对于非总线宽度的访存指令,比如字节操作,实际上不仅不会提高效率,反而有可能降低效率(不一定,需要视处理器的内部实现而定)。

虽然看上去你省掉了一条and指令的时间,但寄存器操作指令的速度与访存操作的速度相差至少几个数量级,而且CISC的处理器,几乎总会在访问非总线宽度的存储器时额外的操作,也就是说,即使软件不写成and 0xff,而写成字节的操作,但硬件实际上还是会执行一次位掩码操作,以保证指令的逻辑正确性。要知道,实际硬件是没有办法真正地实现“字节”宽度的总线操作周期的!总线宽度是32位的话,每个总线周期在总线上出现的数据总是32位!

另外,NOP指令当然要占CPU的指令周期了。你可以查看一下INTEL的指令手册,看看为NOP指令存在的必要性。
2008-11-23 17:26
0
雪    币: 2368
活跃值: (81)
能力值: (RANK:300 )
在线值:
发帖
回帖
粉丝
4
书呆子果然是专业出生。每次回答都充满的专业气息。
遗憾本人只是草莽出生...哎.
再次膜拜...
2008-11-23 18:12
0
雪    币: 723
活跃值: (81)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
5
汗~ LZ 所贴的例子,本意与不是书呆彭所说的原因

00402F00  |.  8B8424 9C000000   |mov     eax, dword ptr [esp+9C]
00402F07  |.  8B8C24 94000000   |mov     ecx, dword ptr [esp+94]
00402F0E  |.  25 FF000000       |and     eax, 0FF
00402F13  |.  81E1 FF000000     |and     ecx, 0FF
00402F19  |.  8A9441 90DD9900   |mov     dl, byte ptr [ecx+eax*2+99DD90]
00402F20  |.  8A8448 90DD9900   |mov     al, byte ptr [eax+ecx*2+99DD90]

本意,只要 [esp+9c] 和 [esp+94] 一个字节就够了。
所在要清掉高位

如果 mov al, byte ptr [esp+9c]       // 不可以保证整个 eax 寄存器的值是中高位是 0
所以: mov     dl, byte ptr [ecx+eax*2+99DD90]  中的 eax 值得不到保证。

--------------------------------
要说也应该说在自然边界对齐吧
2008-11-23 21:33
0
雪    币: 2110
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
6
[QUOTE=盛全;539969]实际上 [esp+9C]和 [esp+94] 是unsigned char变量,其值肯定不会大于0xFF
而且程序中有大量的连续的这代码段,在已经在以保证eax/acx 里除al/cl 里可能在数据,其它都是0的情况下
[/QUOTE]

唉,语文不行,经mik指出,我又把楼主这几句话反复读了几遍,好像确实是理解错楼主问的意思了。。。

我以为楼主要表达的意思是问下面的两条指令

mov eax, [esp+9c]
and eax,0xff

与下面一条指令

movzx eax, byte ptr [esp+9c]

哪个效率高,所以我回答的意思是”一条指令并不一定比两条指令就效率高“,相反还有可能(但不一定)低一些。

结果看mik的回答,我才明白楼主似乎是想问

mov al, [esp+9c]

这条指令能不能保证eax中高24位全为0。

-_-!

以后答题需要先审题啊。。。
2008-11-23 22:59
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
谢谢大家的解答。

这是从一个函数中截取的部分代码,也许大家没想到的是这个函数应用十分广泛,十个人里最少有九个人用过。
这个函数通过本人的优化,到目前为止,我见过的其它人做的最快的那个也不及我做的1/3速度(这个主要是算法上的优化)。
这个函数可用于运算最密集的穷举上,所以效率很重要。
在一楼贴的那段代码在这个函数中出现近百次。所占汇编代码行数为整个函数的80%并在函数内多次循环。所以我觉得有必要进一步去探索优化的方法。

to dzhsurf:
影响程序的效率通常不在这些小处上,而是在算法的选择上。
这个道理我明白,但是如果在算法上已经没有提高的空间的情况下,为提高效率你不得把最细致的问题都考虑进去。我见过不少的程序员在运算最密集的代码编写上是用了汇编,可见编译系统不见所有的代码都很高明。

to 彭总:
谢谢指教,您让我明白了NOP也是开销CPU的。但是有一点我还是不明白,还是再抖胆再请教一下,按您的说法:
mov     eax, dword ptr [esp+9C]

mov     al, byte ptr [esp+9C]
高效的话,我想 mov al XXXX是没有存在的意义的。
但事实上mov al XXXX是存在并广泛使用的,而且我N年前我道听途说的听过mov al XXXX 是比 mov ax XXXX 高效的。别人还告诉我如果用char 型可以解决的问题尽量不要用int 。
做成这样后:
00402F00      8A8424 9C000000   mov     al, byte ptr [esp+9C]
00402F07      8A8C24 94000000   mov     cl, byte ptr [esp+94]
00402F0E      90                nop
00402F0F      90                nop
00402F10      90                nop
00402F11      90                nop
00402F12      90                nop
00402F13      90                nop
00402F14      90                nop
00402F15      90                nop
00402F16      90                nop
00402F17      90                nop
00402F18      90                nop
00402F19  |.  8A9441 90DD9900   |mov     dl, byte ptr [ecx+eax*2+99DD90]
00402F20  |.  8A8448 90DD9900   |mov     al, byte ptr [eax+ecx*2+99DD90]

我实测没有影响正确性(当然执行这段代码时我已经确保eax和ecx之前不大于0xFF)。速度并没有慢,与原来基本一样,也许是NOP的原因,既然NOP开销CPU可以把它删掉。但是后面的代码前移了要解决跳转的问题。我打算把NOP去掉再试试速度如何。

00402F00      8A8424 9C000000   mov     al, byte ptr [esp+9C]
00402F07      8A8C24 94000000   mov     cl, byte ptr [esp+94]
00402F19  |.  8A9441 90DD9900   |mov     dl, byte ptr [ecx+eax*2+99DD90]
00402F20  |.  8A8448 90DD9900   |mov     al, byte ptr [eax+ecx*2+99DD90]

另外如果用VS2005的话编译出来的代码全部都是整版的movez XXX XXX。VS2005的比VC60的慢了将近一倍。

to mik:

如果 mov al, byte ptr [esp+9c]       // 不可以保证整个 eax 寄存器的值是中高位是 0
所以: mov     dl, byte ptr [ecx+eax*2+99DD90]  中的 eax 值得不到保证。

你提起的可能的问题我已经说明是有满足eax不大于0xFF的前提。
这段代码重复出现近100次,只要分辨出eax 在何时有大于0xFF的值时,要开始执行那段代码的时候别忘了&0xFF,因为那段代码一但出现总会重重复复的,只要开头时没忘了&0xFF,之后eax在很长的一段代码里是不会大于0xFF的。之后的&0xFF是不是就可以省了?这样看来就不会有问题了吧?
2008-11-23 23:54
0
雪    币: 2110
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
8
[QUOTE=盛全;540273]mov     eax, dword ptr [esp+9C]

mov     al, byte ptr [esp+9C]
高效的话,我想 mov al XXXX是没有存在的意义的。
但事实上mov al XXXX是存在并广泛使用的,而且我N年前我道听途说的听过mov al XXXX 是比 mov ax XXXX 高效的。别人还告诉我如果用char 型可以解决的问题尽量不要用int 。[/QUOTE]

关于代码优化,如果你是在开发某种库,确有其必要,否则不必花太多精力在这上面。

我说的意思,并不是说mov al, xxxx是没有意义的。也不是说谁一定就比谁快。

影响指令执行效率的因素是非常多的,INTEL的不同型号的CPU都不保证同一条指令的内部实现保持不变。

通常情况下,mov eax,xxxx与mov al, xxxx的效率不相上下。但是INTEL的资料中指出,存在一种叫做“部分阻塞”的流水线相关,在这种情况下,使用部分寄存器会降低流水线的效率。

今天我写了一个小程序,你看一下,代码很容易理解:

#include <ctime>
#include <iostream>
#include <iomanip>
#include <windows.h>

LARGE_INTEGER	t0,t1;


void	PrintTime(void)
{
	LARGE_INTEGER	tm;
			tm.QuadPart	= t1.QuadPart-t0.QuadPart;

	std::cout	<< std::hex		<< std::setfill('0')
			<< "\nStart time:\t"	<< std::setw(8)	<< t0.HighPart	<< std::setw(8)	<< t0.LowPart
			<< "\nFinish time:\t"	<< std::setw(8)	<< t1.HighPart	<< std::setw(8)	<< t1.LowPart
			<< "\nCost time:\t"	<< std::setw(8)	<< tm.HighPart	<< std::setw(8)	<< tm.LowPart
			<< std::endl;
}
_declspec(naked) void	StartMeassure(void)
{
	__asm
	{
		rdtsc;
		mov	t0.LowPart,	eax;
		mov	t0.HighPart,	edx;
		ret
	}
}

_declspec(naked) void	EndMeassure(void)
{
	__asm
	{
		rdtsc;
		mov	t1.LowPart,	eax;
		mov	t1.HighPart,	edx;
		ret

	}
}



volatile union
{
	unsigned long	_dword;
	unsigned char	_byte[4];
}u0,u1;


int	main(void)
{

	register unsigned int	i;

	// 本实验为特殊情况,不具有代表性,只在一定程度上说明问题
	// 用不同的指令组合去实现以下功能,测试其执行时间。
	// 第一次先用C来描述所执行的功能。

	u0._dword	= 0x12345678;

	std::cout	<< "Test Starting:"	<< std::endl;
	// 用C描述的版本1,
	::StartMeassure();
	for ( i = 0; i < 1000000000u; ++i )
	{
		u1._dword	= (unsigned long) u0._byte[0];
	}
	::EndMeassure();
	::PrintTime();

	// 用C描述的版本2
	::StartMeassure();
	for ( i = 0; i < 1000000000u; ++i )
	{
		u1._dword	= 0;
		u1._byte[0]	= u0._byte[0];
	}
	::EndMeassure();
	::PrintTime();

	// 用C描述的版本3,
	::StartMeassure();
	for ( i = 0; i < 1000000000u; ++i )
	{
		u1._byte[0]	= u0._byte[0];
		u1._byte[1]	= 0;
		u1._byte[2]	= 0;
		u1._byte[3]	= 0;
	}
	::EndMeassure();
	::PrintTime();


	// 汇编:使用MOVZX EAX,BYTE PTR XXX
	::StartMeassure();
	for ( i = 0; i < 1000000000u; ++i )
	{
		__asm
		{

			movzx	eax,		byte ptr u0._byte[0];
			mov	u1._dword,	eax
		}
	}
	::EndMeassure();
	::PrintTime();

	// 汇编:使用MOV EAX,XXX和AND EAX, 0XFF
	::StartMeassure();
	for ( i = 0; i < 1000000000u; ++i )
	{
		__asm
		{
			mov	eax,		u0._dword;
			and	eax,		0xFF
			mov	u1._dword,	eax
		}
	}
	::EndMeassure();
	::PrintTime();

	// 汇编:使用MOV AL,XXX
	::StartMeassure();
	__asm		xor	eax,		eax;

	for ( i = 0; i < 1000000000u; ++i )
	{
		__asm
		{
			mov	al,		u0._byte[0];
			mov	u1._dword,	eax
		}
	}
	::EndMeassure();
	::PrintTime();

	::system("PAUSE");

	return	0;
}


在我的机器上用VC6的编译器,开了编译器O2优化,输出结果如下:

Test Starting:

Start time:     0001450c6922dcc0
Finish time:    0001450d6d479f10
Cost time:      000000010424c250

Start time:     0001450d6d7c7874
Finish time:    0001450ea15c6c8c
Cost time:      0000000133dff418

Start time:     0001450ea1a701b8
Finish time:    00014510552d83f4
Cost time:      00000001b386823c

Start time:     000145125822a7f4
Finish time:    000145130a8818b0
Cost time:      00000000b26570bc

Start time:     000145130abf634c
Finish time:    00014513be9cf474
Cost time:      00000000b3dd9128

Start time:     00014513bed4f0f4
Finish time:    00014514b4a505d0
Cost time:      00000000f5d014dc


虽然这只能说明一种情况下的执行时间比较,但也能说明某些问题。对编译器不信任,在汇编一级调试,发现循环内部并没有插入多余代码,因此结果具有可比性。

你也可以自己编译运行一下看看结果。

需要说明的是,如果字节地址是在非对齐的地址上,那么使用mov al,xxxx或movzx eax,xxxx的执行效率基本不变,但使用mov eax,xxx / and eax 0xff 访问则效率下降非常大,因为此时需要两个总线周期。

另外我想说几句关于代码优化的事:绝对不要相信任何经验,只有实验才能说明问题。

经验不仅具有主观性,最主要的是它具有局限性,今天正确的事,明天不一定就正确。

举几个例子:

1.lodsb 指令与        mov  al, [esi] / add esi, 4组合哪个执行更快?

2.loop 指令与  dec  ecx /  jnz 组合哪个执行更快?

3.jecxz指令与 test  ecx, ecx/  jz 组合哪个更快?

4. 到达字符串的末尾的以下两段代码
   lea esi, [ebp + asciiz]              ;6 bytes
      s_check: lodsb                                ;1 byte
                test al, al                          ;2 bytes
                jne s_check                          ;2 bytes

        Super's method:


      
 lea edi, [ebp + asciiz]              ;6 bytes
                xor al, al                            ;2 bytes
      s_check: scasb                                ;1 byte
                jne s_check                          ;2 byte 


哪个更快?

对前3个问题,至少经过我的测试,在我的奔腾D上是后者(即两条指令组合)更快,但很多讲优化的书都说前一种快(可能386上确实前者快)。

对于第4个,我没有测试,但是据说后者在386以前的机器上快,前者在486以后的机器上更快。

但是,即便是代码执行效率确实不同,问题是这么优化有什么意义吗???这完全是出力不讨好,而且极有可能出错。

我的观点是,没必要在“效率”问题上钻牛角。dzhsurf说得很对,影响效率最主要是算法。所谓“优化”,并不是重点。

我N年前我道听途说的听过mov al XXXX 是比 mov ax XXXX 高效的。


别说N年前,就是上个月,它也不一定是正确的。回到8088/86的时代,8088总线是8位的,当然al比ax快,而且快得多,但8086就已经是16位总线了,它们就没什么差别了。

别人还告诉我如果用char 型可以解决的问题尽量不要用int


不知道是这话是哪里来的。至少我所看到的关于C++的书中,全部都说,“除非是嵌入式领域等对内存斤斤计较的场合,否则尽量使用与机器字宽度相同的整数类型,而不要为了节约内存使用短的类型”,原因是,”与机器字宽度相同的整数类型更加有利于编译器进行优化“!

写了很多,都只是我个人的经验,所以如果有人指出错误之处,我一定虚心接受。

PS.我的专业恰恰是关于硬件的,所以对流水、相关、总线周期等概念比较熟,倒是对于软件,我属于“非专业”。
2008-11-24 03:35
0
雪    币: 723
活跃值: (81)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
9
书呆彭:
你的测试时间方法打印出来不直观,改一改吧,用 QueryPerformanceCounter() 函数,精度和 rdstc 指令差不多:

union {
    int i;
    char c[4];
} u;

void foo1()
{
LARGE_INTEGER l,start,end;

double interval;
double freq;

QueryPerformanceFrequency(&l);
freq = (double)l.QuadPart;

int i = 0;
int d;
char c;

QueryPerformanceCounter(&start);

for ( i = 0; i < 1000000000u; ++i ) {
_asm{
movzx eax, byte ptr u.i
mov d, eax
}
}


QueryPerformanceCounter(&end);

interval = (double)(end.QuadPart - start.QuadPart);

printf("at foo1(): time is: %g ms\n", interval*1000/freq);

}


用 byte 复制肯定比 dword 慢

用 movzx eax, byte ptr u.i  
     mov d, eax

   mov eax, u.i
    and eax,0xff
    mov d, eax
2008-11-24 09:37
0
雪    币: 2110
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
10
[QUOTE=mik;540327]书呆彭:
你的测试时间方法打印出来不直观,改一改吧,用 QueryPerformanceCounter() 函数,精度和 rdstc 指令差不多:

union {
    int i;
    char c[4];
} u;

用 byte 复制肯定比 dword 慢

...[/QUOTE]

多放mik兄提醒,我之前没有注意到还有QueryPerformanceCounter()这个函数。

不过在我的测试中,在地址对齐的情况下,mov eax, u.i / and eax, 0xFF 与 movzx eax, byte ptr u.c[0]的效率基本相当,没发现哪个比哪个慢。

但是在未对齐地址上操作,movzx指令执行效率基本没发生变化,但mov eax指令执行效率则下降非常多。

所以个人以为,如果非要钻牛角尖去考虑效率,还是movzx的用法更好一些。

我查了一下INTEL ARCHITECTURE OPTIMIZATIN REGERENCE MANUAL,是在第二章中Instruction selection -> Operand Sizes 一节,讲了我所提到的“部分寄存器相关”的内容。

手册中给的指导是,对32位寄存器(如EAX)操作,避免对部分寄存器(如AL,AX,AH等)操作而引起的“部分寄存器相关”。对于MOV系列的指令,使用MOVZX,避免使用MOVSX

个人认为,过分地强调“优化”,是不好的编程习惯。合理的优化是通过改进算法和数据结构完成的。至于指令的选择、指令调度以及内存对齐等细节交给编译器就行了。何必花100的精力去换回10的效率提升呢?
2008-11-24 11:16
0
雪    币: 723
活跃值: (81)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
11
movzx 与 movsx 是两个相反的操作。
movzx 是零扩展,movsx 是符号扩展,movzx 是不能代替 movsx 的
2008-11-24 11:25
0
雪    币: 723
活跃值: (81)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
12
对于 movzx eax, byte ptr



这样取单字节的指令来说,对齐对它是没有影响的 (对不对齐结果一样)

是因为,它本身就是只取1个字节。

若是: mov eax, dword ptr

 对这种取多个字节的指令来说,才有影响。

那是因为:若不对齐,则要跨 dword 边界,不对齐,它需要做 2 次取操作。

2008-11-24 11:33
0
雪    币: 2110
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
13
我知道movzx和movsx的区别

我的意思是说,在手册里写了,movzx没有对上下文的流水线相关,但movsx就有。

我想表达的意思是说,看上去差不多的指令,对CPU的执行的影响实际是很复杂的。所以不能绝对地说如何如何就会提高效率,如何如何就执行得慢。

对于地址对齐,可能我不会表达,我想说的意思其实和你的意思一样。我在8楼的回复中已经说了:


需要说明的是,如果字节地址是在非对齐的地址上,那么使用mov al,xxxx或movzx eax,xxxx的执行效率基本不变,但使用mov eax,xxx / and eax 0xff 访问则效率下降非常大,因为此时需要两个总线周期。


所以楼主这个问题的结论应该是:

使用movzx eax, byte ptr [esp+9c]是最恰当的。

还是谢谢mik了。
2008-11-24 11:44
0
雪    币: 723
活跃值: (81)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
14
按理说,movzx 与 movsx 物理上实现是一样简单的。我看了指令周期也一样呀

我刚翻了下 intel optimization 手册没找着这方面论述。

请教书呆彭兄,给我说说看
2008-11-24 12:01
0
雪    币: 2110
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
15
我也没有完全理解,但是手册上第二章的 Assembly/Compier Coding Rule 47 是这么说的,所以我就抄来了。

另外,手册上提到,在Pentium M处理器中,movsx和movzx都只需要一个微操作(micro-op);在Pentium 4处理器上,movsx花费耗额外一个微操作。所以它们的指令周期并不一定相同。
2008-11-24 12:25
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
谢谢各位指点。

合理的优化是通过改进算法和数据结构完成的。

这个再怎么做都是有极限的,不是吗?这方面的工作我已经做了很多,我已经找不到挖掘的空间了。

优化程序的意义我已经说了,这个函数可以说有30%以上的人每天都在用。做这个不为钱,只是追求一种完美,只是说明一个问题。

//////////////////////////////////////////////////////////////
//试验代码
void main()

{
int arraya[16][16][16],arrayb[16][16][16],i,j,k,m;

getch();
m=arraya[i][j][k]=arrayb[j][k][i];

arraya[i][j][k]=m=arrayb[j][k][i];

}
//////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////
//在VC中查看查看汇编代码
2:    void main()
3:
4:    {
0040D490   push        ebp
0040D491   mov         ebp,esp
0040D493   mov         eax,8054h
0040D498   call        $$$00001 (004010e0)
0040D49D   push        ebx
0040D49E   push        esi
0040D49F   push        edi
0040D4A0   lea         edi,[ebp-8054h]
0040D4A6   mov         ecx,2015h
0040D4AB   mov         eax,0CCCCCCCCh
0040D4B0   rep stos    dword ptr [edi]
5:    int arraya[16][16][16],arrayb[16][16][16],i,j,k,l,m;
6:
7:    getch();
0040D4B2   call        _getch (0040d6a0)
8:    m=arraya[i][j][k]=arrayb[j][k][i];
0040D4B7   mov         eax,dword ptr [ebp-8008h]
0040D4BD   shl         eax,0Ah
0040D4C0   lea         ecx,[ebp+eax-8000h]
0040D4C7   mov         edx,dword ptr [ebp-800Ch]
0040D4CD   shl         edx,6
0040D4D0   add         ecx,edx
0040D4D2   mov         eax,dword ptr [ebp-8004h]
0040D4D8   shl         eax,0Ah
0040D4DB   lea         edx,[ebp+eax-4000h]
0040D4E2   mov         eax,dword ptr [ebp-8008h]
0040D4E8   shl         eax,6
0040D4EB   add         edx,eax
0040D4ED   mov         eax,dword ptr [ebp-800Ch]
0040D4F3   mov         esi,dword ptr [ebp-8004h]
0040D4F9   mov         ecx,dword ptr [ecx+esi*4]
0040D4FC   mov         dword ptr [edx+eax*4],ecx
0040D4FF   mov         edx,dword ptr [ebp-8008h]
0040D505   shl         edx,6
0040D508   mov         eax,dword ptr [ebp-8004h]
0040D50E   shl         eax,0Ah
0040D511   lea         ecx,[ebp+eax-4000h]
0040D518   add         ecx,edx
0040D51A   mov         edx,dword ptr [ebp-800Ch]
0040D520   mov         eax,dword ptr [ecx+edx*4]
0040D523   mov         dword ptr [ebp-8014h],eax
9:
10:   arraya[i][j][k]=m=arrayb[j][k][i];
0040D529   mov         ecx,dword ptr [ebp-8008h]
0040D52F   shl         ecx,0Ah
0040D532   lea         edx,[ebp+ecx-8000h]
0040D539   mov         eax,dword ptr [ebp-800Ch]
0040D53F   shl         eax,6
0040D542   add         edx,eax
0040D544   mov         ecx,dword ptr [ebp-8004h]
0040D54A   mov         edx,dword ptr [edx+ecx*4]
0040D54D   mov         dword ptr [ebp-8014h],edx
0040D553   mov         eax,dword ptr [ebp-8004h]
0040D559   shl         eax,0Ah
0040D55C   lea         ecx,[ebp+eax-4000h]
0040D563   mov         edx,dword ptr [ebp-8008h]
0040D569   shl         edx,6
0040D56C   add         ecx,edx
0040D56E   mov         eax,dword ptr [ebp-800Ch]
0040D574   mov         edx,dword ptr [ebp-8014h]
0040D57A   mov         dword ptr [ecx+eax*4],edx
11:
12:   }
/////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////
源码中的:
m=arraya[i][j][k]=arrayb[j][k][i];

arraya[i][j][k]=m=arrayb[j][k][i];

两行代码实现的功能是完全一样的。但是从VC6的编译结果来看,
VC6第一行高级语言代码编译成机器码时反汇编来看是25行。
而后一行高级语言代码编译成机器码时反汇编来看是是18行。
如果直接用汇编相对V60生成的机器码都可以有提高效率的空间。
这从一个侧面反映了编译器并不总是高明的,有时也很呆板。
/////////////////////////////////////////////////////////////

/////////////////////////////////////////
还有我认为这些方法:

  for ( i = 0; i < 1000000000u; ++i )
  {
    u1._dword  = (unsigned long) u0._byte[0];
  }

  for ( i = 0; i < 1000000000u; ++i )
  {
    //u1._dword  = 0;
    u1._byte[0]  = u0._byte[0];
  }

用于实测,可能离我们的测试的目标有比较大的差距。

要知道循环本身就是开销CPU的。
  for ( i = 0; i < 1000000000u; ++i )
  {
    mov     eax, dword ptr [esp+9C]
  }
>>
就算是最简单的循环方法也不过是

        mov         ecx 1000000000                //只做一次,不考虑它
loop1:        mov     eax, dword ptr [esp+9C]        //正常开销 假设开销两个CPU周期
        dec        ecx                          //额外的开销 假设开销一个CPU周期
        jnz          loop1                          //额外的开销 假设开销一个CPU周期

但是:

  for ( i = 0; i < 1000000000u; ++i )
  {
    mov     al, btye ptr [esp+9C]
  }
>>
就算是最简单的循环方法也不过是

        mov         ecx 1000000000                //只做一次,不考虑它
loop1:        mov     al, btye ptr [esp+9C]        //正常开销 假设开销一个CPU周期
        dec        ecx                          //额外的开销 假设开销一个CPU周期
        jnz          loop1                          //额外的开销 假设开销一个CPU周期

如果满足上面的假设,本来
mov     al, btye ptr [esp+9C]

mov     eax, dword ptr [esp+9C]
是相差一倍的。

但是使用这个方法再统计时间的话。第一段是开销是4个单位的CPU 第二段是开销的是3个单位的CPU
成绩比是4/3 而不是真正的2/1

所以说这种方法的误差可能很大。

////////////////////////////////

还有就是我听说:
mov     al, byte ptr [esp+9C]
and     eax, 0FF

movzx   al byte ptr [esp+9C]
要高效。
/////////////////////////////////

我打算自己做个实测,但这几天挺忙的,可能不会很快,测好后再发上来。
2008-11-24 13:02
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
////////////////////////////////
还有就是我听说:
mov     al, byte ptr [esp+9C]
and     eax, 0FF

movzx   al byte ptr [esp+9C]
要高效。
/////////////////////////////////

这段有笔误。应该改为:
////////////////////////////////
还有就是我听说:
mov     eax, dword ptr [esp+9C]
and     eax, 0FF

movzx   eax, byte ptr [esp+9C]
要高效。
/////////////////////////////////
2008-11-24 13:12
0
雪    币: 723
活跃值: (81)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
18
不知你看的是哪个版本的 intel optimization 手册

最新的是:
Assembly/Compiler Coding Rule 47. (H impact, M generality) A load that
forwards from a store must have the same address start point and therefore the
same alignment as the store data.

说的是:对同一个地址 stroe 接着 load 情况下对齐促进性能
2008-11-24 13:22
0
雪    币: 2110
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
19
测试执行时间只是为了对比不同的指令组合的相对执行时间的差别。

我不是反对优化。需要分情况。如果优化后只能提高20%的性能,我宁可不要这10%了。

除非有证据证明,优化后的程序执行性能有了明显的提高,比如说有50%,我才可能会考虑去做这种底层优化。

To mik:

我的手册是2005版的,呵呵,有点旧了

其中 Assembly/Compiler Coding Rule 47.(M impack, M generality)的原文是:

Try to use zero extension or operate on 32-bit operands instead of using moves with sign extension, 2-77


我查了一下,你书中的RULE 47在我书上的编号是18。

唉,看来我这书有点落伍了。
2008-11-24 14:40
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
天啊~~加上 and eax ,0xFF 反而变快了!!!这是为什么???
前一段代码没有and eax ,0xFF 耗时1989.71 ms
后一段代码加了and eax ,0xFF 耗时1602.22 ms
(P4M 1G CPU)

  for ( i = 0; i < 100000000u; ++i ) //0x5F5E100
  {
          _asm
          {
                mov eax, dword ptr u.i

                mov c, al
                mov eax, dword ptr u.i

                mov c, al
                mov eax, dword ptr u.i

                mov c, al
                mov eax, dword ptr u.i

                mov c, al
                mov eax, dword ptr u.i

                mov c, al
                mov eax, dword ptr u.i

                mov c, al
                mov eax, dword ptr u.i

                mov c, al
                mov eax, dword ptr u.i

                mov c, al
                mov eax, dword ptr u.i

                mov c, al
                mov eax, dword ptr u.i

                mov c, al
          }
  }
  at foo1(): time is: 1989.71 ms
  ///////////////////////////////////////

  for ( i = 0; i < 100000000u; ++i ) //0x5F5E100
  {
          _asm
          {
                mov eax, dword ptr u.i
                and eax ,0xFF
                mov c, al
                mov eax, dword ptr u.i
                and eax ,0xFF
                mov c, al
                mov eax, dword ptr u.i
                and eax ,0xFF
                mov c, al
                mov eax, dword ptr u.i
                and eax ,0xFF
                mov c, al
                mov eax, dword ptr u.i
                and eax ,0xFF
                mov c, al
                mov eax, dword ptr u.i
                and eax ,0xFF
                mov c, al
                mov eax, dword ptr u.i
                and eax ,0xFF
                mov c, al
                mov eax, dword ptr u.i
                and eax ,0xFF
                mov c, al
                mov eax, dword ptr u.i
                and eax ,0xFF
                mov c, al
                mov eax, dword ptr u.i
                and eax ,0xFF
                mov c, al

          }
  }

  at foo1(): time is: 1602.22 ms
  ///////////////////////////////////

//////////////////
movzx al  XXX
mov   eax XXX
mov   al  XXX
实测速度基本没有区别
/////////////////
2008-11-24 15:37
0
雪    币: 2110
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
21
所以我说所谓“优化”,绝对不要相信任何经验,当然更不可想当然。

按我的理解,and eax,0xFF这条指令使它前面对eax的读和后面对al的写的指令依赖关系打破,所以CPU的乱序执行和指令调度可以最大限度地发挥作用

而不如果不加and  eax,0xff,CPU的流水线控制单元认为上下两条两条指令存在相关(实际上读-写相关属于流水线伪相关,但它同样影响流水调度),从而拒绝重新调度指令,因此执行反面慢了。
2008-11-24 21:56
0
游客
登录 | 注册 方可回帖
返回
//