内联汇编语言比本机C ++代码慢吗?
我试图比较内联汇编语言和C ++代码的性能,所以我编写了一个函数,它可以将两个2000大小的数组添加到100000次。 代码如下:
#define TIMES 100000
void calcuC(int *x,int *y,int length)
{
for(int i = 0; i < TIMES; i++)
{
for(int j = 0; j < length; j++)
x[j] += y[j];
}
}
void calcuAsm(int *x,int *y,int lengthOfArray)
{
__asm
{
mov edi,TIMES
start:
mov esi,0
mov ecx,lengthOfArray
label:
mov edx,x
push edx
mov eax,DWORD PTR [edx + esi*4]
mov edx,y
mov ebx,DWORD PTR [edx + esi*4]
add eax,ebx
pop edx
mov [edx + esi*4],eax
inc esi
loop label
dec edi
cmp edi,0
jnz start
};
}
这里是main()
:
int main() {
bool errorOccured = false;
setbuf(stdout,NULL);
int *xC,*xAsm,*yC,*yAsm;
xC = new int[2000];
xAsm = new int[2000];
yC = new int[2000];
yAsm = new int[2000];
for(int i = 0; i < 2000; i++)
{
xC[i] = 0;
xAsm[i] = 0;
yC[i] = i;
yAsm[i] = i;
}
time_t start = clock();
calcuC(xC,yC,2000);
// calcuAsm(xAsm,yAsm,2000);
// for(int i = 0; i < 2000; i++)
// {
// if(xC[i] != xAsm[i])
// {
// cout<<"xC["<<i<<"]="<<xC[i]<<" "<<"xAsm["<<i<<"]="<<xAsm[i]<<endl;
// errorOccured = true;
// break;
// }
// }
// if(errorOccured)
// cout<<"Error occurs!"<<endl;
// else
// cout<<"Works fine!"<<endl;
time_t end = clock();
// cout<<"time = "<<(float)(end - start) / CLOCKS_PER_SEC<<"n";
cout<<"time = "<<end - start<<endl;
return 0;
}
然后我运行程序五次以获得处理器的周期,这可以看作是时间。 每次我只打一个上面提到的函数。
结果就是这里。
组装版本的功能:
Debug Release
---------------
732 668
733 680
659 672
667 675
684 694
Average: 677
C ++版本的功能:
Debug Release
-----------------
1068 168
999 166
1072 231
1002 166
1114 183
Average: 182
发布模式下的C ++代码比汇编代码快3.7倍。 为什么?
我猜想我写的汇编代码不如GCC生成的汇编代码有效。 对于像我这样的普通程序员来说,编写代码比编译器产生的对手更快,这很难。这意味着我不应该相信我手写的汇编语言的性能,专注于C ++而忘记汇编语言?
是的,大多数时候。
首先,你从一个错误的假设开始,即低级语言(在这种情况下是汇编)总是会产生比高级语言(本例中为C ++和C)更快的代码。 这不是真的。 C代码总是比Java代码快吗? 不,因为还有另一个变量:程序员。 您编写代码和架构细节知识的方式会极大地影响性能(正如您在本例中看到的那样)。
您始终可以创建一个手工汇编代码比编译代码更好的示例,但通常它是一个虚构的示例或一个单一的例程,而不是真正的500,000行C ++代码程序)。 我认为编译器会产生更好的汇编代码95%的时间, 有时候,只有极少数情况下,您可能需要编写汇编代码,用于少量,短时间,高度使用的性能关键例程,或者当您必须访问您最喜爱的高级语言不公开。 你想要了解这种复杂性吗? 在这里阅读这个令人敬畏的答案。
为什么这个?
首先,因为编译器可以做我们甚至无法想象的优化(见这个简短列表),他们会在几秒钟内完成它们(当我们需要几天的时间)。
在汇编代码时,您必须使用定义良好的调用接口来定义明确的函数。 但是他们可以考虑整个程序的优化和程序间优化,如寄存器分配,常量传播,通用子表达式消除,指令调度以及其他复杂而不明显的优化(例如Polytope模型)。 在RISC架构方面,很多年前,人们不再担心这种情况(例如,指令调度很难手工调整),现代CISC CPU也有很长的管道。
对于一些复杂的微控制器,甚至系统库都是用C语言编写的,而不是汇编语言,因为它们的编译器会生成更好的(且易于维护)最终代码。
编译器有时可以自动使用一些MMX / SIMDx指令,如果你不使用它们,你根本无法比较(其他答案已经很好地检查了你的汇编代码)。 就循环而言,这是一个简短的循环优化列表,用于编译器通常检查的内容(当您的计划已经决定了C#程序时,您认为自己可以自己完成循环优化吗?)如果您在汇编中编写某些内容,认为你必须考虑至少一些简单的优化。 数组的教科书示例是展开循环(其大小在编译时已知)。 做到这一点,并再次运行你的测试。
现在,由于另一个原因,需要使用汇编语言也很少见:大量不同的CPU。 你想支持他们吗? 每个都有一个特定的微体系结构和一些特定的指令集。 它们具有不同数量的功能单元,应该安排汇编指令使它们全部忙碌。 如果你用C语言编写,你可能会使用PGO,但是在汇编中,你需要对该特定架构有深入的了解(并且重新思考和重做所有其他架构 )。 对于小型任务,编译器通常做得更好,而对于复杂任务,通常工作不会被偿还(编译器无论如何可能会做得更好)。
如果你坐下来看看你的代码,你可能会发现重新设计你的算法比翻译成汇编更有用(在这里阅读这篇伟大的文章),还有高级优化(和提示编译器),你可以在需要使用汇编语言之前有效地应用。 可能值得一提的是,经常使用内在函数,您将会获得性能,并且编译器仍然可以执行大部分优化。
所有这一切都表明,即使产生5到10倍的汇编代码,您也应该询问客户是否愿意支付一周的时间或购买50美元更快的CPU 。 通常情况下,大多数人并不需要极其优化(尤其是在LOB应用程序中)。
你的汇编代码非常差,可能会有所改善:
loop
指令,在大多数现代CPU上已知死loop
缓慢(可能是使用古代装配书*的结果) 因此,除非您大大提高了汇编程序的技能,否则编写性能汇编代码是没有意义的。
*当然,我不知道你是否真的从古代集会书中得到loop
指令。 但是你几乎从来没有在真实世界的代码中看到它,因为每个编译器都足够聪明,不会发出loop
,你只能在恕我直言的坏书和过时的书中看到它。
即使在深入研究汇编之前,也存在更高级别的代码转换。
static int const TIMES = 100000;
void calcuC(int *x, int *y, int length) {
for (int i = 0; i < TIMES; i++) {
for (int j = 0; j < length; j++) {
x[j] += y[j];
}
}
}
可以转换成通过循环旋转:
static int const TIMES = 100000;
void calcuC(int *x, int *y, int length) {
for (int j = 0; j < length; ++j) {
for (int i = 0; i < TIMES; ++i) {
x[j] += y[j];
}
}
}
就内存区域而言,这更好。
这可以进一步优化,做a += b
X次相当于做a += X * b
所以我们得到:
static int const TIMES = 100000;
void calcuC(int *x, int *y, int length) {
for (int j = 0; j < length; ++j) {
x[j] += TIMES * y[j];
}
}
不过看起来我最喜欢的优化器(LLVM)没有执行这个转换。
[编辑]我发现,如果我们有改造进行restrict
预选赛x
和y
。 事实上,如果没有这个限制, x[j]
和y[j]
可能会混淆到相同的位置,导致这种转换错误。 [结束编辑]
无论如何,我认为这是优化的C版本。 它已经更简单了。 基于这个,这里是我对ASM的破解(我让Clang生成它,我对它毫无用处):
calcuAsm: # @calcuAsm
.Ltmp0:
.cfi_startproc
# BB#0:
testl %edx, %edx
jle .LBB0_2
.align 16, 0x90
.LBB0_1: # %.lr.ph
# =>This Inner Loop Header: Depth=1
imull $100000, (%rsi), %eax # imm = 0x186A0
addl %eax, (%rdi)
addq $4, %rsi
addq $4, %rdi
decl %edx
jne .LBB0_1
.LBB0_2: # %._crit_edge
ret
.Ltmp1:
.size calcuAsm, .Ltmp1-calcuAsm
.Ltmp2:
.cfi_endproc
我害怕我不明白所有这些指令来自哪里,但是你总是可以玩得开心,试着去看看它是如何比较的......但我仍然使用优化的C版本,而不是程序集中的代码,更便携。
链接地址: http://www.djcxy.com/p/86745.html上一篇: Is inline assembly language slower than native C++ code?