我使用GCC的"asm"
关键字在C中编写了一个简单的乘法函数,并在汇编代码中编写了另一个。
我花了他们每个人的执行时间,尽管他们的时间非常接近,但C函数比汇编代码中的C函数要快一些。
我想知道为什么,因为我期望ASM会更快。是因为对GCC的“ asm”关键字进行了额外的“调用”(我不知道使用哪个单词)?
这是C函数:
int multiply (int a, int b){return a*b;}
这是C文件中的asm
:
int asmMultiply(int a, int b){
asm ("imull %1,%0;"
: "+r" (a)
: "r" (b)
);
return a;
}
我主要的时间:
int main(){
int n = 50000;
clock_t asmClock = clock();
while(n>0){
asmMultiply(4,5);
n--;
}
asmClock = clock() - asmClock;
double asmTime = ((double)asmClock)/CLOCKS_PER_SEC;
clock_t cClock = clock();
n = 50000;
while(n>0){
multiply(4,5);
n--;
}
cClock = clock() - cClock;
double cTime = ((double)cClock)/CLOCKS_PER_SEC;
printf("Asm time: %f\n",asmTime);
printf("C code time: %f\n",cTime);
谢谢!
答案 0 :(得分:3)
汇编函数比C函数做更多的工作-初始化mult
,然后进行乘法并将结果分配给mult
,然后将值从mult
推入返回位置。
编译器擅长优化;您不会轻易在基本算术上击败他们。
如果您确实需要改进,请使用static inline int multiply(int a, int b) { return a * b; }
。或者只需在调用代码中写a * b
(或等效代码)而不是int x = multiply(a, b);
。
答案 1 :(得分:0)
这种微基准测试在几乎所有方面都太幼稚了,您无法获得有意义的结果。
即使您解决了表面问题(因此代码也没有得到优化),在得出任何有关asm
何时会比*
更好的结论之前,仍然存在一些重大的深层问题。 / p>
(提示:可能永远不会。编译器已经知道如何最佳地乘以整数,并了解该操作的语义。强制其使用imul
而不是自动向量化或执行其他操作优化将是一种损失。)
两个定时区域均为空,因为两个乘法都可以优化。 (asm
不是asm volatile
,并且您不使用结果。)您仅在测量噪声和/或将CPU频率提升到{{1 }}。
即使不是,clock()
指令基本上也无法用imul
这样的开销来测量。也许如果您在clock()
之前用lfence
进行序列化以强制CPU等待imul
退出,请参阅RDTSCP in NASM always returns the same value
或者您在禁用优化的情况下进行编译,这毫无意义。
在没有某种涉及循环的上下文的情况下,您基本上无法测量C rdtsc
和内联汇编程序。然后,将是针对该上下文,具体取决于您通过使用嵌入式asm击败了哪些优化。 (如果要阻止编译器优化纯C版本的工作,该怎么办?)
对于一个x86指令仅测量一个数字并不能告诉您太多有关它的信息。您需要测量延迟,吞吐量和前端uop成本,以正确表征其成本。现代的x86 CPU是超标量的无序流水线,因此2条指令的成本总和取决于它们是否相互依赖以及其他周围环境。 How many CPU cycles are needed for each assembly instruction?
对函数的独立定义是相同的,在进行了更改以允许编译器选择寄存器之后,并且您的asm 可以内联的方式有些有效,但这仍然不利于优化。 gcc知道在编译时5 * 4 = 20,因此如果您使用结果*
可以优化为立即数multiply(4,5)
。但是gcc不知道asm的作用,因此它至少必须将输入一次提供给它。 (不过,非20
表示如果您在循环中使用volatile
则可以对结果进行CSE。)
因此,内联asm击败了持续传播。即使只有一个输入是常量,而另一个是运行时变量,这也很重要。许多小的整数乘法器可以用一个或2条LEA指令或一个移位(与现代x86上asmMultiply(4,5)
的3c相比,延迟较低)来实现。
https://gcc.gnu.org/wiki/DontUseInlineAsm
我能想象到的唯一用例imul
的帮助是,如果编译器在实际上是前端绑定的情况下使用2x LEA指令,其中asm
会让它与1个uop而不是2个。但是您的asm消除了使用立即数的可能性(您只允许使用寄存器约束),并且GNU C内联不能让您对立即数和寄存器arg使用不同的模板。也许您对仅寄存器部分使用了多个替代约束和匹配的寄存器约束?但是,不,您仍然必须拥有类似imul $constant, %[src], %[dst]
之类的东西,但它不适用于reg,reg。
您可以使用asm("%2, %1, %0" :...)
,它将与GCC配合使用以击败LEA。还是无论如何都需要一个常数乘数,因为您只想将其用于特定的gcc版本,以解决特定的遗漏优化问题。 (就是这么小巧,实际上您不会这样做。)
您的代码on the Godbolt compiler explorer ,其中if(__builtin_constant_p(a)) { asm using imul-immediate } else { return a*b; }
用于x86-64 System V调用约定:
clang7.0 -O3
TL:DR:如果您想了解这种细粒度的内联asm性能,则需要首先了解编译器如何进行优化。