在编程语言中,对比不同编程风格的代码写法,或者通过使用不同的编译器和编译优化参数,通过编译器生成汇编代码,静态分析所生成汇编代码的运行效率。
在window 7平台下,采用vc++ 6.0编译器来编写相应的C程序,然后通过UltraCompare工具来比较vc++编译程序后产生的汇编文件的异同(win-TC在win7 64位下无法正常运行)。
方向:
· 1. 相同编译器下相同编译参数,不同的源代码写法(源代码结果一样,但实现不同,例如递归和通常的实现等)的对比;
代码1:(for循环实现的代码)
void main()
{
int i,n=0;
for(i=0; i<10000;i++)
{
n++;
}
printf("%d",n);
}
代码2:(递归实现的代码)
int recursion(int n, int i)
{
if(i>0)
{
n++;
i--;
n = recursion(n,i);
}
else
{
return n;
}
return n;
}
void main()
{
int k;
k = recursion(0,10000);
printf("%d",k);
}
通过转换为汇编语言,对比以上代码的异同(通过UlTraCompare)。
首先是得到的汇编代码(通过VC++得到的)
For循环对应的汇编代码:
TITLE E:\资料\各科资料\嵌入式软件开发\c程序\1.c
.386P
includelisting.inc
if @Version gt510
.model FLAT
else
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
_DATA SEGMENT DWORD USE32 PUBLIC 'DATA'
_DATA ENDS
CONST SEGMENT DWORD USE32 PUBLIC 'CONST'
CONST ENDS
_BSS SEGMENT DWORD USE32 PUBLIC 'BSS'
_BSS ENDS
$$SYMBOLS SEGMENT BYTE USE32 'DEBSYM'
$$SYMBOLS ENDS
$$TYPES SEGMENT BYTE USE32 'DEBTYP'
$$TYPES ENDS
_TLS SEGMENT DWORD USE32 PUBLIC 'TLS'
_TLS ENDS
; COMDAT ??_C@_02MECO@?$CFd?$AA@
CONST SEGMENT DWORD USE32 PUBLIC 'CONST'
CONST ENDS
; COMDAT _main
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
FLAT GROUP _DATA, CONST, _BSS
ASSUME CS:FLAT, DS: FLAT, SS: FLAT
endif
PUBLIC _main
PUBLIC ??_C@_02MECO@?$CFd?$AA@ ; `string'
EXTRN _printf:NEAR
EXTRN __chkesp:NEAR
; COMDAT ??_C@_02MECO@?$CFd?$AA@
; File E:\资料\各科资料\嵌入式软件开发\c程序\1.c
CONST SEGMENT
??_C@_02MECO@?$CFd?$AA@DB '%d', 00H ;`string'
CONST ENDS
; COMDAT _main
_TEXT SEGMENT
_i$ = -4
_n$ = -8
_main PROC NEAR ;COMDAT
; 4 : {
push ebp
mov ebp,esp
sub esp,72 ;00000048H
push ebx
push esi
push edi
lea edi,DWORD PTR [ebp-72]
mov ecx,18 ;00000012H
mov eax,-858993460 ;ccccccccH
rep stosd
; 5 : inti,n=0;
mov DWORDPTR _n$[ebp], 0
; 6 : for(i=0;i<10000; i++)
mov DWORDPTR _i$[ebp], 0
jmp SHORT$L339
$L340:
mov eax,DWORD PTR _i$[ebp]
add eax,1
mov DWORDPTR _i$[ebp], eax
$L339:
cmp DWORDPTR _i$[ebp], 10000 ; 00002710H
jge SHORT$L341
; 8 : n++;
mov ecx,DWORD PTR _n$[ebp]
add ecx,1
mov DWORDPTR _n$[ebp], ecx
; 9 : }
jmp SHORT$L340
$L341:
; 10 : printf("%d",n);
mov edx,DWORD PTR _n$[ebp]
push edx
push OFFSETFLAT:??_C@_02MECO@?$CFd?$AA@ ;`string'
call _printf
add esp,8
; 11 : }
pop edi
pop esi
pop ebx
add esp,72 ;00000048H
cmp ebp,esp
call __chkesp
mov esp,ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS
END
递归代码的汇编代码:
TITLE E:\资料\各科资料\嵌入式软件开发\c程序\2.c
.386P
includelisting.inc
if @Version gt510
.model FLAT
else
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
_DATA SEGMENT DWORD USE32 PUBLIC 'DATA'
_DATA ENDS
CONST SEGMENT DWORD USE32 PUBLIC 'CONST'
CONST ENDS
_BSS SEGMENT DWORD USE32 PUBLIC 'BSS'
_BSS ENDS
$$SYMBOLS SEGMENT BYTE USE32 'DEBSYM'
$$SYMBOLS ENDS
$$TYPES SEGMENT BYTE USE32 'DEBTYP'
$$TYPES ENDS
_TLS SEGMENT DWORD USE32 PUBLIC 'TLS'
_TLS ENDS
; COMDAT ??_C@_02MECO@?$CFd?$AA@
CONST SEGMENT DWORD USE32 PUBLIC 'CONST'
CONST ENDS
; COMDAT _recursion
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
; COMDAT _main
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
FLAT GROUP _DATA, CONST, _BSS
ASSUME CS:FLAT, DS: FLAT, SS: FLAT
endif
PUBLIC _recursion
EXTRN __chkesp:NEAR
; COMDAT _recursion
_TEXT SEGMENT
_n$ = 8
_i$ = 12
_recursion PROCNEAR ;COMDAT
; 4 : {
push ebp
mov ebp,esp
sub esp,64 ;00000040H
push ebx
push esi
push edi
lea edi,DWORD PTR [ebp-64]
mov ecx,16 ; 00000010H
mov eax,-858993460 ;ccccccccH
rep stosd
; 5 : if(i>0)
cmp DWORDPTR _i$[ebp], 0
jle SHORT$L341
; 7 : n++;
mov eax,DWORD PTR _n$[ebp]
add eax,1
mov DWORDPTR _n$[ebp], eax
; 8 : i--;
mov ecx,DWORD PTR _i$[ebp]
sub ecx,1
mov DWORDPTR _i$[ebp], ecx
; 9 : n= recursion(n,i);
mov edx,DWORD PTR _i$[ebp]
push edx
mov eax,DWORD PTR _n$[ebp]
push eax
call _recursion
add esp,8
mov DWORDPTR _n$[ebp], eax
; 11 : else
jmp SHORT$L342
$L341:
; 13 : returnn;
mov eax,DWORD PTR _n$[ebp]
jmp SHORT$L340
$L342:
; 15 : returnn;
mov eax,DWORD PTR _n$[ebp]
$L340:
; 16 : }
pop edi
pop esi
pop ebx
add esp,64 ;00000040H
cmp ebp,esp
call __chkesp
mov esp,ebp
pop ebp
ret 0
_recursion ENDP
_TEXT ENDS
PUBLIC _main
PUBLIC ??_C@_02MECO@?$CFd?$AA@ ; `string'
EXTRN _printf:NEAR
; COMDAT ??_C@_02MECO@?$CFd?$AA@
; File E:\资料\各科资料\嵌入式软件开发\c程序\2.c
CONST SEGMENT
??_C@_02MECO@?$CFd?$AA@DB '%d', 00H ;`string'
CONST ENDS
; COMDAT _main
_TEXT SEGMENT
_k$ = -4
_main PROC NEAR ;COMDAT
; 19 : {
push ebp
mov ebp,esp
sub esp,68 ;00000044H
push ebx
push esi
push edi
lea edi,DWORD PTR [ebp-68]
mov ecx,17 ;00000011H
mov eax,-858993460 ;ccccccccH
rep stosd
; 20 : intk;
; 21 : k =recursion(0,10000);
push 10000 ; 00002710H
push 0
call _recursion
add esp,8
mov DWORDPTR _k$[ebp], eax
; 22 : printf("%d",k);
mov eax,DWORD PTR _k$[ebp]
push eax
push OFFSETFLAT:??_C@_02MECO@?$CFd?$AA@ ;`string'
call _printf
add esp,8
; 23 : }
pop edi
pop esi
pop ebx
add esp,68 ;00000044H
cmp ebp,esp
call __chkesp
mov esp,ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS
END
通过UltaCompare比较:
图1:
图2
两段代码分别通过循环(for)、递归来实现一个值为0的变量累加到10000的过程。
抛开两端源代码在C文件的比较中存在差异不说,只是通过Ultracompare比较两段汇编代码,会发现,较之for循环,在递归的汇编代码中,存在大量的push、pop操作,可见,在运行递归算法程序时,计算机要对相应的参数进行不断的出栈、入栈操作,以保存递归的上一层的参数。每累加一次,都要做一次相应的操作,10000次的累加操作就要进行10000次出入栈的操作,消耗巨大,程序运行时间长,影响机器的运行效率。
另外,仔细观察汇编代码,会发现,相比于循环的代码,递归代码中要使用更多的寄存器,同样会对机器的运行效率影响较大。
递归运行可能在某些大数据量的程序中使用的话虽然说对程序员来说很方便,只需要调用相关的函数(方法)即可,但是对于机器来说却是高消耗的,而且效率低,不管是应用程序还是科学验证程序,不管是从用户体验还是机器底层结构来看,都是要慎重考虑的。
从算法的角度来看,应该活用动态规划以及贪心算法,不能死套递归。
想起之前在算法课的综合性试验,老师让我们就一个问题,通过不同的算法去实现,同事比较各个算法的运行时间的差别,结果出来发现,1000000级的运算量,动态规划算法在30秒以内就可完成,但是递归方法就要用到5分钟的运算时间,而且期间电脑CPU发热很高。