use*_*121 174 c c++ performance assembly
我试图比较内联汇编语言和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
};
}
Run Code Online (Sandbox Code Playgroud)
这是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;
}
Run Code Online (Sandbox Code Playgroud)
然后我运行程序五次以获得处理器的周期,这可以被视为时间.每次我只调用上面提到的功能之一.
这是结果.
Debug Release
---------------
732 668
733 680
659 672
667 675
684 694
Average: 677
Run Code Online (Sandbox Code Playgroud)
Debug Release
-----------------
1068 168
999 166
1072 231
1002 166
1114 183
Average: 182
Run Code Online (Sandbox Code Playgroud)
发布模式下的C++代码几乎是汇编代码的3.7倍.为什么?
我想我写的汇编代码不如GCC生成的那样有效.像我这样的普通程序员很难比编译器生成的对手更快地编写代码.这意味着我不应该相信我手写的汇编语言的表现,专注于C++而忘记汇编语言?
Adr*_*tti 244
是的,大多数时候.
首先,你从一个错误的假设开始,即低级语言(在这种情况下是汇编)总是会产生比高级语言更快的代码(在这种情况下是C++和C).这不是真的.C代码总是比Java代码快吗?不,因为还有另一个变量:程序员.编写代码和体系结构细节知识的方式极大地影响了性能(正如您在本例中所看到的).
您总是可以创建一个示例,其中手工汇编代码比编译代码更好,但通常它是一个虚构的示例或单个例程,而不是500.000+行C++代码的真正程序).我认为编译器会产生95%的更好的汇编代码,有时候,只有极少数情况下,您可能需要编写汇编代码,用于少数,简短,高度使用,性能关键的例程,或者当您必须访问您喜欢的高级语言的功能时没有曝光.你想要触及这种复杂性吗?在SO上阅读这个很棒的答案.
为什么这个?
首先,因为编译器可以进行我们甚至无法想象的优化(参见这个简短的列表),他们将在几秒钟内完成它们(当我们可能需要几天时).
在汇编代码中进行编码时,必须使用定义良好的调用接口来创建定义良好的函数.然而,它们可以考虑整个程序优化和过程间优化,例如寄存器分配,常量传播,公共子表达式消除,指令调度和其他复杂的,不明显的优化(例如,Polytope模型).在RISC架构上,人们多年前就不再担心这种情况(例如,指令调度很难手动调整),现代CISC CPU也有很长的管道.
对于一些复杂的微控制器,甚至系统库都是用C语言而不是汇编编写的,因为它们的编译器产生了更好(且易于维护)的最终代码.
编译器有时可以自动使用一些MMX/SIMDx指令,如果你不使用它们就无法比较(其他答案已经很好地检查了汇编代码).只是for循环这是一个循环优化的简短列表,通常由编译器检查(你认为你可以自己在为C#程序决定你的日程安排时自己做吗?)如果你在汇编中写一些东西,我认为你必须考虑至少一些简单的优化.数组的学校书籍示例是展开循环(其大小在编译时已知).做它并再次运行测试.
现在,出于另一个原因需要使用汇编语言也是非常罕见的:过多的不同CPU.你想支持他们吗?每个都有一个特定的微体系结构和一些特定的指令集.它们具有不同数量的功能单元,并且应该安排组装指令以使它们全部忙碌.如果你用C语言编写,你可能会使用PGO,但是在汇编中你需要对该特定体系结构有很好的了解(并重新考虑并重做另一个体系结构的所有内容).对于小任务,编译器通常会做得更好,对于复杂的任务,通常不会回报工作(并且编译器可能会做得更好).
如果你坐下来看看你的代码,你可能会发现重新设计你的算法会比转换到汇编获得更多(在SO上阅读这篇精彩文章),有高级优化(和提示编译器)您可以在需要使用汇编语言之前有效地应用.值得一提的是,经常使用内在函数,您将获得正在寻找的性能增益,编译器仍然可以执行大部分优化.
所有这些都说,即使你能够生成5到10倍快的汇编代码,你应该问你的客户他们是否愿意支付一周的时间或购买50美元更快的CPU.我们大多数人根本不需要极端优化(特别是在LOB应用程序中).
Gun*_*iez 189
您的汇编代码非常差,
略微不理想,可能会有所改进:
loop指令在大多数现代CPU上已知都很慢(可能是因为使用了古老的装配书*)因此,除非您大大提高了有关汇编程序的技能,否则编写汇编代码以提高性能是没有意义的.
*当然,我不知道你是否真的得到loop了古代装配书的指示.但是你几乎从来没有在现实世界的代码中看到它,因为那里的每个编译器都足够聪明,不会发出loop,你只能在恕我直言中看到它们过时和过时的书籍.
Mat*_* M. 59
甚至在深入研究装配之前,存在更高级别的代码转换.
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];
}
}
}
Run Code Online (Sandbox Code Playgroud)
可以通过循环旋转转换为:
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];
}
}
}
Run Code Online (Sandbox Code Playgroud)
就内存位置而言,这要好得多.
这可以进一步优化,做a += bX次相当于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];
}
}
Run Code Online (Sandbox Code Playgroud)
但似乎我最喜欢的优化器(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
Run Code Online (Sandbox Code Playgroud)
我担心我不明白所有这些指令来自何处,但是你总是可以玩得开心并尝试看看它是如何比较的...但我仍然使用优化的C版本而不是汇编版本,在代码中,更便携.
Oli*_*rth 41
简短回答:是的.
答案很长:是的,除非你真的知道你在做什么,并且有理由这样做.
sas*_*sha 33
我修复了我的asm代码:
__asm
{
mov ebx,TIMES
start:
mov ecx,lengthOfArray
mov esi,x
shr ecx,1
mov edi,y
label:
movq mm0,QWORD PTR[esi]
paddd mm0,QWORD PTR[edi]
add edi,8
movq QWORD PTR[esi],mm0
add esi,8
dec ecx
jnz label
dec ebx
jnz start
};
Run Code Online (Sandbox Code Playgroud)
发布版本的结果:
Function of assembly version: 81
Function of C++ version: 161
Run Code Online (Sandbox Code Playgroud)
发布模式下的汇编代码几乎比C++快2倍.
jal*_*alf 24
这是否意味着我不应该相信我手中编写的汇编语言的表现
是的,这正是它的意思,对每种语言都是如此.如果您不知道如何在X语言中编写高效的代码,那么您就不应该相信自己在X中编写高效代码的能力.因此,如果您想要高效的代码,则应该使用其他语言.
装配对此特别敏感,因为,你所看到的就是你得到的.您编写了您希望CPU执行的特定指令.使用高级语言,有一个编译器,它可以转换您的代码并消除许多低效率.有了装配,你就可以自己动手了.
for*_*ran 21
现在使用汇编语言的唯一原因是使用语言无法访问的某些功能.
这适用于:
但是当前的编译器非常聪明,它们甚至可以替换两个单独的语句,就像d = a / b; r = a % b;单个指令一样
,如果可用的话,计算除法和余数,即使C没有这样的运算符.
小智 19
确实,现代编译器在代码优化方面做得非常出色,但我仍然鼓励你继续学习汇编.
首先,你显然没有被它吓倒,这是一个伟大的,伟大的优势,接下来 - 你正在通过剖析以验证或放弃你的速度假设,你要求有经验的人和你的输入拥有人类已知的最好的优化工具: 大脑.
随着您的体验增加,您将学习何时何地使用它(通常在您的算法级别进行深度优化后,代码中最紧密,最内层的循环).
为了寻找灵感,我会建议你查找迈克尔·亚伯拉什的文章(如果你还没有听到他的消息,他是一个优化大师,他甚至与约翰·卡马克在地震软件渲染器的优化合作!)
"没有最快的代码" - 迈克尔·阿布拉什
sas*_*sha 14
我更改了asm代码:
__asm
{
mov ebx,TIMES
start:
mov ecx,lengthOfArray
mov esi,x
shr ecx,2
mov edi,y
label:
mov eax,DWORD PTR [esi]
add eax,DWORD PTR [edi]
add edi,4
dec ecx
mov DWORD PTR [esi],eax
add esi,4
test ecx,ecx
jnz label
dec ebx
test ebx,ebx
jnz start
};
Run Code Online (Sandbox Code Playgroud)
发布版本的结果:
Function of assembly version: 41
Function of C++ version: 161
Run Code Online (Sandbox Code Playgroud)
发布模式下的汇编代码几乎是C++的4倍.IMHo,汇编代码的速度取决于程序员
Nun*_*147 12
大多数高级语言编译器都经过了优化,并且知道它们在做什么.您可以尝试转储反汇编代码并将其与本机程序集进行比较.我相信你会看到你的编译器正在使用的一些不错的技巧.
仅举例来说,即使我不确定它是否正确:):
这样做:
mov eax,0
Run Code Online (Sandbox Code Playgroud)
花费更多的周期
xor eax,eax
Run Code Online (Sandbox Code Playgroud)
它做同样的事情.
编译器知道所有这些技巧并使用它们.
小智 12
这是非常有趣的话题!
我在Sasha的代码中用SSE更改了MMX
这是我的结果:
Function of C++ version: 315
Function of assembly(simply): 312
Function of assembly (MMX): 136
Function of assembly (SSE): 62
Run Code Online (Sandbox Code Playgroud)
使用SSE的汇编代码比C++快5倍
har*_*old 10
编译器打败了你.我试试看,但我不会保证.我将假设TIMES的"乘法"意味着使它成为一个更相关的性能测试,y并且x是16对齐的,并且length是4的非零倍数.无论如何,这可能都是真的.
mov ecx,length
lea esi,[y+4*ecx]
lea edi,[x+4*ecx]
neg ecx
loop:
movdqa xmm0,[esi+4*ecx]
paddd xmm0,[edi+4*ecx]
movdqa [edi+4*ecx],xmm0
add ecx,4
jnz loop
Run Code Online (Sandbox Code Playgroud)
就像我说的,我不保证.但是如果它可以更快地完成我会感到惊讶 - 这里的瓶颈就是内存吞吐量,即使一切都是L1命中.
只是盲目地实现完全相同的算法,逐个指令,在汇编中保证比编译器可以做的慢.
这是因为即使是编译器所做的最小优化也比刚刚完成优化的严格代码要好.
当然,有可能击败编译器,特别是如果它是代码的一个小的,本地化的部分,我甚至不得不亲自去做一个约.4倍加速,但在这种情况下,我们必须严重依赖对硬件的良好了解和众多看似反直觉的技巧.
小智 5
作为编译器,我将用固定大小的循环替换许多执行任务。
int a = 10;
for (int i = 0; i < 3; i += 1) {
a = a + i;
}
Run Code Online (Sandbox Code Playgroud)
将产生
int a = 10;
a = a + 0;
a = a + 1;
a = a + 2;
Run Code Online (Sandbox Code Playgroud)
最终它将知道“ a = a + 0;” 是没有用的,所以它将删除此行。希望您现在脑子里有些东西愿意附加一些优化选项作为评论。所有这些非常有效的优化将使编译的语言更快。
| 归档时间: |
|
| 查看次数: |
52522 次 |
| 最近记录: |