注册 登录
编程论坛 VB6论坛

VB代码执行效率问题:abs(负数) 与 0-负数,那条语句执行更快?

wufuzhang 发布于 2018-05-03 14:18, 4062 次点击
代码一:
Dim lngX As Long
Dim lngY As Long
If lngX<0 then
   lngY=Abs(lngX)
Else
   lngY=lngX
End if

代码二:
Dim lngX As Long
Dim lngY As Long
lngY=lngX
If lngX<0 then   lngY=0-lngX

这两段代码,哪条执行比较快?还是差不多?

13 回复
#2
wufuzhang2018-05-03 15:04
==================
主要是不知道VB里面Abs()函数是怎么实现的,
0-负数是怎么实现的,求哪位大神指点一下。
#3
wmf20142018-05-03 15:08
测试后结果是:IDE环境下运行abs效率高,编译后执行if判断的效率高。
测试代码如下:
Private Sub Command1_Click()
  Dim t As Double, a As Long, b As Long, i As Long
  a = -1234
  t = Timer
  For i = 0 To 100000000
    b = a
    If a < 0 Then b = -a
  Next
  MsgBox Timer - t
End Sub

Private Sub Command2_Click()
  Dim t As Double, a As Long, b As Long, i As Long
  a = -1234
  t = Timer
  For i = 0 To 100000000
    b = Abs(a)
  Next
  MsgBox Timer - t
End Sub

#4
wds12018-05-03 15:31
当然是0-lngX快。
不论怎么实现,abs都需要额外调用内部函数。
#5
wufuzhang2018-05-03 15:35
我把代码补全了测试结果是:IDE环境下运行Abs函数效率更高(1.4S左右,另一种方法1.7S左右),
编译后Abs函数效率更低了(0.35S左右,另一种方法0.15S左右)
@wmf2014回答得没错
现在问题来了,为什么IDE环境和编译后,两段代码执行的效率居然相反呢?
测试代码:
Private Sub Command1_Click()
  Dim t As Double, a As Long, b As Long, i As Long
  a = -1234
  t = Timer
  For i = 0 To 100000000
    b = a
    If a < 0 Then b = 0 - a
  Next
  MsgBox Timer - t
End Sub

Private Sub Command2_Click()
  Dim t As Double, a As Long, b As Long, i As Long
  a = -1234
  t = Timer
  For i = 0 To 100000000
    If a < 0 Then
       b = Abs(a)
    Else
       b = a
    End If
  Next
  MsgBox Timer - t
End Sub
#6
wufuzhang2018-05-03 15:38
回复 4楼 wds1
可以亲测,IDE环境和编译后,两段代码执行效率相反。
#7
风吹过b2018-05-03 16:54
测试代码:
程序代码:
Private Sub Command1_Click()
  Dim t As Double, a As Long, b As Long, i As Long
  Dim s As String
  
  a = -1234
  t = Timer
  For i = 0 To 100000000
    b = a
    If a < 0 Then b = 0 - a
  Next
  s = "方法一耗时:" & (Timer - t)
  
  a = -1234
  t = Timer
  For i = 0 To 100000000
    If a < 0 Then
       b = Abs(a)
    Else
       b = a
    End If
  Next
  s = s & vbCrLf & "方法二耗时:" & (Timer - t)
  
  a = -1234
  t = Timer
  For i = 0 To 100000000
    If a < 0 Then
        b = -a
    Else
        b = a
    End If
  Next
  s = s & vbCrLf & "方法三耗时:" & (Timer - t)
  
  Text1.Text = s
End Sub


IDE环境运行结果:
方法一耗时:1.66900000000157
方法二耗时:1.29599999999988
方法三耗时:1.26999999999779


编译为本机代码运行结果:
方法一耗时:.116000000001165
方法二耗时:.212000000002796
方法三耗时:.0450000000032595


编译为伪代码运行结果:
方法一耗时:1.22499999999837
方法二耗时:1.04100000000326
方法三耗时:1.23399999999855


从我测试的结果来看,认真优化后的代码,不管IDE还是编译成本机代码
if 都比 abs 要快。

你方法一中:分析均忽略需要生成的寻址
    b = a                      产生 内存复制代码
    If a < 0 Then b = 0 - a    产生条件跳转和一条内存复制代码

我优化后,方法三中:
    If a < 0 Then          产生条件跳转
        b = -a             一条内存复制,和一条无条件转移命令
    Else
        b = a              第二条内存复制命令,但这二条内存复制命令只会执行一条,
    End If

无条件转移命令,好像是只需二个时钟周期,内存复制,二个寻码,一个复制,我不知道了。
等于减掉了一次内存复制,增加了一次无条件转移,节省了不少的时钟周期。
#8
风吹过b2018-05-03 16:59
为啥 IDE 环境与 编译后 时间有这么大的差距,
我认为原因在于 VB6 的IDE环境,还是 解释执行 造成的。
编译伪代码是 解释执行的。

去掉所有检查编译成本机代码运行结果
方法一耗时:5.00000000029672E-02
方法二耗时:3.10000000000592E-02
方法三耗时:3.09999999973805E-02


说明VB生成的代码,会加入很多检测代码在内,每多一步运算,都要经过一批检查。
#9
wds12018-05-03 17:23
方法1的循环内多了 b = a过程,两者比较是不准的。

相同过程情况,方法1速度快。

在开发环境,两者效率应该差不多,因为都需要解释执行,所以速度几乎一致。

在编译环境,方法1虽然增加了赋值过程,但是由于没有调用abs,总体时差就显示出来了。  

#10
风吹过b2018-05-03 17:56
CPU Disasm
地址        十六进制数据            汇编代码                                                    注释
00401C1E   .  B9 00E1F505   mov ecx,5F5E100                                         ; 循环终止值
00401C23   .  BA 01000000   mov edx,1                                               ; 1
00401C28   .  33C0          xor eax,eax                                             ; 循环变量起始清零
00401C2A   >  3BC1          cmp eax,ecx                                             ; 比较循环变量与终止值,循环体开始
00401C2C   .  7F 1C         jg short 00401C4A                                       ; 是否跳出循环
00401C2E   .  3BFE          cmp edi,esi                                             ; 是否小于零
00401C30   .  7D 0A         jge short 00401C3C                                      ; 小于零跳
00401C32   .  8BDF          mov ebx,edi                                             ; EDT到EBX
00401C34   .  F7DB          neg ebx                                                 ; 求补指令,相当于0-这个数
00401C36   .  0F80 33020000 jo 00401E6F                                             ; 是否溢出
00401C3C   >  8BDA          mov ebx,edx
00401C3E   .  03D8          add ebx,eax                                             ; 计算循环变量
00401C40   .  0F80 29020000 jo 00401E6F                                             ; 是否溢出
00401C46   .  8BC3          mov eax,ebx                                             ; 保存循环变量
00401C48   .^ EB E0         jmp short 00401C2A


CPU Disasm
地址        十六进制数据            汇编代码                                                    注释
00401CAD   > /8B4D E8       mov ecx,dword ptr [ebp-18]                              ; 循环计数读到 ecx中
00401CB0   . |B8 00E1F505   mov eax,5F5E100                                         ; 循环终值读到 eax中
00401CB5   . |3BC8          cmp ecx,eax                                             ; 比较是否到达循环次数
00401CB7   . |7F 22         jg short 00401CDB                                       ; 达到循环次数跳循环外
00401CB9   . |8B4D E4       mov ecx,dword ptr [ebp-1C]                              ; b = a
00401CBC   . |85C9          test ecx,ecx                                            ; 测试
00401CBE   . |7D 06         jge short 00401CC6                                      ; 符号为零,则跳转
00401CC0   . |FF15 1C104000 call dword ptr [<&MSVBVM60.__vbaI4Abs>]                 ; 调用 abs 函数,没跟入
00401CC6   > |8B4D E8       mov ecx,dword ptr [ebp-18]                              ; 循环计数读到ECX中
00401CC9   . |B8 01000000   mov eax,1                                               ; 把1放到EAX中
00401CCE   . |03C1          add eax,ecx                                             ; EAX+ECX
00401CD0   . |0F80 99010000 jo 00401E6F                                             ; 加法是否溢出,溢出跳出错
00401CD6   . |8945 E8       mov dword ptr [ebp-18],eax                              ; 循环结果放内存
00401CD9   .^\EB D2         jmp short 00401CAD                                      ; 返回去循环


CPU Disasm
地址        十六进制数据            汇编代码                                                    注释
00401D49   .  B9 00E1F505   mov ecx,5F5E100                                         ; 循环终值
00401D4E   .  33C0          xor eax,eax                                             ; 清零eax
00401D50   >  3BC1          cmp eax,ecx                                             ; 比较循环变量,ECX中终止值
00401D52   .  7F 11         jg short 00401D65                                       ; 跳出循环
00401D54   .  BA 01000000   mov edx,1                                               ; edx 放1
00401D59   .  03D0          add edx,eax                                             ; 循环变量+1
00401D5B   .  0F80 0E010000 jo 00401E6F                                             ; 是否溢出
00401D61   .  8BC2          mov eax,edx                                             ; 保存循环变量
00401D63   .^ EB EB         jmp short 00401D50                                      ; 继续循环,天哪,赋值命令去哪了


#11
风吹过b2018-05-03 18:06
0-负数,就是一条指令
neg ebx                                                 ; 求补指令,相当于0-这个数
加上判断也就是4条指令
00401C2E   .  3BFE          cmp edi,esi                                             ; 是否小于零
00401C30   .  7D 0A         jge short 00401C3C                                      ; 小于零跳
00401C32   .  8BDF          mov ebx,edi                                             ; EDT到EBX
00401C34   .  F7DB          neg ebx                                                 ; 求补指令,相当于0-这个数

调用 abs 函数
00401CC0   . |FF15 1C104000 call dword ptr [<&MSVBVM60.__vbaI4Abs>]                 ; 调用 abs 函数

里的内容:
CPU Disasm
地址        十六进制数据            汇编代码                                                    注释
72A24D03    56              push esi
72A24D04    8BF1            mov esi,ecx
72A24D06    85F6            test esi,esi                  测试结果
72A24D08    7D 0B           jge short 72A24D15            
72A24D0A    F7DE            neg esi                       也是使用的这个方法 neg
72A24D0C    79 07           jns short 72A24D15            符号位不为1,则跳
72A24D0E    6A 06           push 6
72A24D10    E8 1795FEFF     call 72A0E22C                 又一层调用,靠。不过在这里没有执行,猜是错误处理过程。
72A24D15    8BC6            mov eax,esi
72A24D17    5E              pop esi
72A24D18    C3              retn



#12
风吹过b2018-05-03 18:09
结合汇编 分析
在IDE解释执行情况下,
IF命令需要解释再执行
而调用 ABS时,直接执行的DLL里的 机器指令,这步比解释执行要快很多。

所以,在IDE下调用VB内置函数,比自己用实现要快。
但编译后,自己实现的函数,只要优化到位,比调用VB内置函数少了调用时的开销。很可能就更快了。

#13
wufuzhang2018-05-04 11:59
回复 10楼 风吹过b
大神就是大神,有理有据,讲的很透彻,我自己也测试了,确实是这样。

我们更关注的是编译后的可执行.exe文件的执行效率,因为这个才是客户的体验。

代码少些一句(b=a),函数少调用一个(abs),我们的程序就会跑的更快一点。

PS:CUP Disasm 后面一大串东东在哪里可以看到?
#14
风吹过b2018-05-04 12:42
CUP Disasm 后面一大串东东在哪里可以看到?
用 OllyDbg 看的。

我也很多命令看不懂,后面的注释都是自己输入 的。
VBDebugger 的结果,结果更好看一点,但有时会自己吃掉命令。
1