http://blog.sina.com.cn/s/blog_6296cebc0100fa5g.html
在VC2008下将32位C++内嵌汇编迁移到64位
——东方射日——
为什么要汇编?
现在正在做的一个项目,是关于高密度计算的,由于计算量很大,从性能考虑,除了算法的优化外,其中的的核心代码是用汇编写的,也许有人说,现在还有用汇编的吗?编译器的优化已经很好了,性能已经非常接近于汇编了,有必要用汇编实现吗?呵呵,再好编译器的优化也没有人工优化好。是的,两者的性能相差不大,可是也有10%-20%的差距啊,尤其是作为核心代码,在一个典型过程中要执行400万到1000万次,那么性能相差是相当明显的。就拿现在的例子来讲,算法优化后,多线程(8核8线程)执行一个过程在C++需要大约62毫秒,而用汇编实现则仅需要53毫秒。
好了,罗嗦了一段为什么要使用汇编的原因,下面进入这题。上述的汇编是采用内嵌汇编在VC2008-x86下实现的,现在要将该工程移植到64位下,字长长了,应该对并行计算大大得有好处,性能可以进一步提高。
在VC2008-x64下编译工程,纯C++配置,编译出来上述的典型过程需要59毫秒,比32位快了5%。
接着打开汇编编译开关,编译内嵌汇编,编译器报告_asm{}语法错误!查资料,原来C++还不支持内嵌的64位汇编。没办法,如果不实现汇编,那么移植是毫无意义的,毕竟还不如32位带汇编的效率高。看来要实现汇编只有一种方法,就是写单独的汇编代码,然后编译为obj文件再链接进原来的工程。
C++到汇编
不过本人所有的汇编经验仅限于内嵌汇编,对于独立的汇编程序,经验趋近于零。继续查资料,OMG,独立的汇编有太多的约定,比如接口定义,各个段的定义和标志,还有不同的调用约定,太麻烦了。而且项目的时间不允许我花上一两个星期来学习。怎么办呢?
VC++再调试中,你可以打开汇编语言页面,跟踪一条条汇编指令,也就是说VC++一定是将源代码先编译为汇编再编译为二进制目标码的,也许编译器有汇编的开关吧,说干就干,打开DOS窗口,到VC++编译器cl的目录下,敲入
cl /?
你在-OUTPUT FILES-一栏中可以看到这个选项:
/Fa[file] name assembly listing file
哈哈,原来还可以直接输出汇编代码。那么有办法了,我就先把核心代码那段拿出来,单独建一个c++文件,去掉所有不相关的头文件,仅将必要的数据类型和常量定义加上,然后在DOS窗口下编译:
cl /c /Fa decoder_asm.cpp
不出所料得到了汇编代码的输出:
decoder_asm.asm
代码内容如下:
; Listing generated by Microsoft (R) Optimizing Compiler Version 15.00.30729.01
include listing.inc
INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES
PUBLIC $T3239
PUBLIC $T3240
:
:
PUBLIC decode_pass0_asm64
pdata SEGMENT
$pdata$decode_pass0_asm64 DD imagerel $LN160
DD imagerel $LN160+7895
DD imagerel $unwind$decode_pass0_asm64
pdata ENDS
xdata SEGMENT
$unwind$decode_pass0_asm64 DD 041d01H
DD 057011dH
DD 060157016H
; Function compile flags: /Odtp
xdata ENDS
_TEXT SEGMENT
width_by3$ = 0
sign_asm64$ = 784
decode_pass0_asm64 PROC
; File c:\jason\decoder\decoder\codec\src\decoder_asm.cpp
; Line 351
$LN160:
mov QWORD PTR [rsp+32], r9
mov DWORD PTR [rsp+24], r8d
mov QWORD PTR [rsp+16], rdx
mov QWORD PTR [rsp+8], rcx
push rsi
push rdi
sub rsp, 696 ; 000002b8H
; Line 366
mov rax, QWORD PTR coder$[rsp]
:
:
decode_pass0_asm64 ENDP
_TEXT ENDS
END
汇编到OBJ
太好了,下一步就是将这个asm文件编译为obj了。
查资料,VC下的64位汇编编译器是ml64
在DOS下敲命令
ml64 /c decoder_asm.asm
报告错误
decoder_asm.asm(3) : fatal error A1000:cannot open file : listing.inc
看上面的汇编代码,要include一个listing.inc文件,这个文件是干什么的?搜搜,在C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\include下有这个文件,号称是:
;; LISTING.INC
;;
;; This file contains assembler macros and is included by the files created
;; with the -FA compiler switch to be assembled by MASM (Microsoft Macro
;; Assembler).
;;
;; Copyright (c) 1993-2003, Microsoft Corporation. All rights reserved.
那么就把这个文件copy到源文件目录下,继续执行编译。
好,那个链接错误消失了
不过这回又多出了若干个错误:
Assembling: decoder_asm.asm
decoder_asm.asm(42) : error A2006:undefined symbol : $LN160
decoder_asm.asm(43) : error A2006:undefined symbol : $LN160
:
:
原来是这行的错误:
$pdata$bmi_t1_decode_pass0_asm64 DD imagerel $LN160
那么$LN160是什么呢?为什么报错呢?
问了下网上的高手,告诉我这个很简单啊,就是一个标号,说在使用在定义前啊
呵呵,原来如此,那么既然是标号,我就野蛮些直接把这些注释掉,就是把上面整个pdata段全注释掉!!
再编译一次,果然编译成功,生成了obj文件。
OBJ链接回工程
下一步就是要将这个obj链接回去工程文件了,那么就是要改VC的工程配置。
首先,第一步建立的那个cpp文件decoder_asm.cpp仅仅是中间文件,我们不希望编译器编译链接的,那么在这个文件上打开Property对话框。在General下将Tool改为Custom Build Tool。(原来是C/C++ Compiler Tool)
现在build,你自然得到几个说什么几个函数找不到的错误。
下一步,打开该project的Property对话框,在Configuration Properties -> Linker -> Input 里的Additional Dependencies里加上上述的obj文件。
接着build。WOW!成功!
写汇编
下一步,我就可以在生成的汇编文件里修改汇编代码,因为所有的变量接口和调用约定都已经做好了,那么接下来的工作就和以前写内嵌汇编没什么差别了。
呵呵,笨人有笨办法,看看,从来没写过独立汇编的人就这样绕过了这一难题。
其他事项
当然还有其他的工作可以做做为以后的调试做准备。
首先可以将生成的汇编加到工程中,然后在Property->General->Tool改为Custom Build Tool。并自己定义一个build的过程,就是上文汇编到OBJ中提到的ml64,那么以后你就可以不用命令窗口而直接在VC下build了。
至于调试,可以就是用VC,没有源代码的部分就用汇编页面看吧——反正源代码也就是汇编。
下一步
今天刚刚生成了汇编的框架,再下一步就是要写汇编了,希望不会遇到什么大问题,有什么问题再和大家交流吧