【VB6 位移操作“第一弹”: 运用 【内联汇编代码 + CallWindowProc】 实现的移位操作】
计算机指令的“移位”操作是比较常见的,机器指令(或者说:CPU、硬件)直接提供对“移位操作”的支持。
很多计算机语言都是直接支持“移位操作”的语句,可以实现高效的移位运算。
但是,在VB6中,却没有“移位操作”的语句和函数支持。因为“没有”,所以在VB6的程序代码中几乎是看不到进行
移位操作的语句的。虽然在VB6中没有移位操作,但并不代表写VB6的程序完全没有这个需求;可是当实实在在的有需要
时,我们只好“绕弯子”去实现移位操作,或者是用别的算法(逻辑)去避开“移位操作”的方式。
如果需要在VB6中实现移位运算的支持,最根本的方法,就是通过“算术运算”来实现了。只是在VB6中没有“无符号”
整数、在算术运算时又有“溢出”问题,因此要“正确移位”,又得去计算“掩码”来屏蔽掉某些bit位。但计算“掩码”
只能是通过“幂运算”得到,这个运算是浮点运算,相对于整数运算来说要多消耗很多时间。记得好像是16倍:在8086芯
片上,一个整数运算是4个时钟周期,(即“1个基本指令周期”),但浮点运算需要64个时钟(也许完成像幂运算这种
“超元函数”需要的开销更大,所以当时就有一个浮点运算协处理器8087专门处理浮点运算)。现在的CPU都是在“流水管
线”中执行指令,很多指令都是“看起来1个时钟1条指令”了,但这浮点运算肯定还没达到这步,估计它这“16个时钟”
是少不了的
(具体不清楚,没看到过关于“流水管线”中各种机器指令所需耗时的资料)。
因此用算术运算来实现移位操作,过程比较复杂,运行效率也比较低。
今天我在这儿分享一份源码,是参阅了网上不少的资料,然后自己整理、封装的一个移位操作接口模块。使用自己构
建的汇编代码(机器码),然后通过API函数CallWindowProc()去调用,执行移位操作,并返回操作结果。当然要说运行效
率,这个方法并不算高(后面有测试数据),因为CallWindowProc()这个API函数,并不是为了给你“通过地址进行函数调
用”而生的,它是“窗口消息操作”相关的函数,在使用时先得把相关参数传递给它,而“在它里面”会做很多有关窗口
操作的一些“准备工作”,因此执行了很多对“移位操作”来说根本无用的指令。虽然在调用时本来是“窗口句柄”的参
数,被一个“被操作数值”替代了,但是CallWindowProc()并不会去验证这个“句柄值”是否真的代表一个有效窗口,因
而我们的“伪造回调函数”才会得以被执行。总体来说,用这种方式实现的移位操作,运行效率还是比用算术运算来模拟
移位的方法高一些。
这个移位操作模块,移位函数共有8个,就是32位/16位的、左移/右移、单向移/循环移,组合起来2×2×2就是8个了,
写这个模块的时候,应该是2013年5月份吧。当然这8个函数,都是基于“逻辑移位”来操作的,因为我觉得“算术移位”似
乎根本用不上。如果你觉得有这个需要,反正我把源码发布出来了,你可以“依葫芦画瓢”自己去添加。更早之前写的那个
“算术运算移位”模块(大概是2009年,不太清楚了,因为以前的代码基本没什么注释),也中封装的这样8个函数。当然,
今天这个源码中,算术移位的只有32位左移和右移的代码,因为“算术移位”仅仅是配角而已。
为了对比测试,才从之前的代码模块中复制了过来。
那个“算术运算移位”的代码,看起来“很不雅”,请大家勿见笑!因为我编写代码比较注重运行效率,当时就是出于
“尽可能快的出结果”,所以在函数是“能得到结果时”就拿到结果并Exit Function。因此函数中有很多Exit Function。
不过也无所谓了,因为这个代码早就成了“尘封的历史”,根本不会再用到它,也就不用去考虑它写得好不好、如何写能够
更好了。按我现在的编程风格,所有的Function几乎是没有“Exit Function”出现的,函数出口都是在“End Function”
那儿。自然,在“End Function”的前面那句,必然都是“函数名 = 返回值”这样的了。
先贴个运行测试的效果图。可以看到,“左移位”的操作,算术运算(“右上角”那块。在下图中按“有焦点的按钮”
识别是哪个操作方法的结果)耗时是API操作(左上)的两倍,因为左移位时,算术运算要考虑溢出问题、必须要单独处理
“移到符号位去”的那一个bit位。而“右移位”,如果是“正整数”(左下),耗时也API操作相当,但是如果是“符号位
不为0”的数值,在VB6中也就是“负数”,那么算术运算移位时又得单独处理一下符号位了,消耗的时间立马出现“翻倍”
(右下)。特殊情况就是“被操作数值为0”和“移位位数为0”的这种情况了,因为可以立即返回结果,所以耗时极短。
为什么这儿不比较“循环移位”呢?对于CPU来说,执行机器指令对数据进行移位,无论是左移、右移、循环移,要移
动多少位,都是没有任何区别的。但是用算术方法实现呢,这就完全不同了,上面已经有“单向移位”的比较结果了,如
果是要进行“循环移位”,无论左循环还是右循环,必须是“两个单向移位的组合操作”,所以不用比,结果都出来了:
总耗时将超过API操作的3到4倍,妥妥的……(当然仍然是“0”除外)。
还有,用“算术运算移位”,大家也可以写自己的算法来比较、测试一下。唯一的要求:不能对操作数、移位位数的
“参数”有任何限制,必须要保证通用、正确。
再说一下“单次调用消耗”,我的是1.7G的双核CPU,并且是“移动版”的,因此速度比较慢点。
按照调用次数、对应耗时进行计算,调用API执行一次移位操作,消耗约270个时钟周期。不过实际应该没有这么多,
虽然CPU是双核四线程,毕竟Win7系统还有四、五十个的后台进程,论“线程数”,肯定是有一百多的了,不可能把CPU资
源全部耗在你这一个进程上。但这也能从侧面反映出,在VB6中要“移位运算”,开销不是一般般的大…………
测试代码如下:
Option Explicit
Private Sub Command1_Click()
Dim t As Double
Dim i As Long
Dim v As Long
Dim n As Long
v = Val(Text1.Text)
n = 10000 * Val(Text2.Text)
t = Timer()
For i = 1& To n
n = LongSHL(v, 15)
Next
t = Timer() - t
Me.Cls
Me.Print "左移位操作: 15位"
Me.Print "运算结果:" & Hex$(n)
Me.Print "运算次数:" & (i - 1&)
Me.Print "消耗时间(ms):" & Int(1000 * t)
End Sub
Private Sub Command2_Click()
Dim t As Double
Dim i As Long
Dim v As Long
Dim n As Long
v = Val(Text1.Text)
n = 10000 * Val(Text2.Text)
t = Timer()
For i = 1& To n
n = LongLShift(v, 15)
Next
t = Timer() - t
Me.Cls
Me.Print "左移位操作: 15位"
Me.Print "运算结果:" & Hex$(n)
Me.Print "运算次数:" & (i - 1&)
Me.Print "消耗时间(ms):" & Int(1000 * t)
End Sub
Private Sub Command3_Click()
Dim t As Double
Dim i As Long
Dim v As Long
Dim n As Long
v = Val(Text1.Text)
n = 10000 * Val(Text2.Text)
t = Timer()
For i = 1& To n
n = LongSHR(v, 15)
Next
t = Timer() - t
Me.Cls
Me.Print "右移位操作: 15位"
Me.Print "运算结果:" & Hex$(n)
Me.Print "运算次数:" & (i - 1&)
Me.Print "消耗时间(ms):" & Int(1000 * t)
End Sub
Private Sub Command4_Click()
Dim t As Double
Dim i As Long
Dim v As Long
Dim n As Long
v = Val(Text1.Text)
n = 10000 * Val(Text2.Text)
t = Timer()
For i = 1& To n
n = LongRShiftLG(v, 15)
Next
t = Timer() - t
Me.Cls
Me.Print "右移位操作: 15位"
Me.Print "运算结果:" & Hex$(n)
Me.Print "运算次数:" & (i - 1&)
Me.Print "消耗时间(ms):" & Int(1000 * t)
End Sub
Private Sub Form_Load()
Call InitASM
Text1.Text = "&H4567ABCD"
Text2.Text = "500"
AutoRedraw = True
End Sub
再贴张图片展示一点代码:
这个运行效率可以说是“实在太低”,有没有能更高效率的实现方法呢?
答案是肯定的:用 COM对象+ASM …………
这个COM对象,当然不是在VB6工程中“加一个class模块”了事,而是用代码自己“制造出来”的,实现起来要复杂点。
最近进行了一下基础测试,效果还不错。效率嘛,说了出来可能会吓你们一跳。当然不知道稳定性如何,还需要进一步
的分析、测试(但估计没什么问题),目前也不准备公布这份源码(给大家留点悬念,哈哈……)
所以呢,在这儿我也学学别的网友,来个什么“第一季、第二季”呀,“第一弹、第二弹”什么的。
在以后合适的情况下,有可能也会公布出来。
等会儿,我把这个工程(主要目的是发布“移位操作接口模块”)上传到资源中,有兴趣的朋友们可以下载。