【发布时间】:2015-12-03 09:48:16
【问题描述】:
这是一小段汇编代码(我使用了 gnu 汇编器的语法)。
.extern cos
.section .data
pi: .double 3.14
.section .text
.global slowcos
.global fastcos
fastcos:
fldl pi
subl $8, %esp # makes some space for a double on the stack
fstpl 0(%esp) # copy pi on top of the stack
call cos
addl $8, %esp
ret
slowcos:
pushl pi+4 # push the last 4 bytes of pi on top of the stack
pushl pi # push the first 4 bytes of pi on top of the stack
call cos
addl $8, %esp
retx
可以使用以下原型从 C 中轻松调用这些符号:
extern double fastcos ();
extern double slowcos ();
它们都返回“cos(3.14)”的值,但在英特尔 32 位架构上,slowcos 比 fastcos 慢两倍。我的问题如下:
什么可以解释如此大的性能差异?
在 linux 上,您可以通过将此代码复制到文件调用 cos.asm 中并调用以下方法来测试:
as --32 cos.asm -o cos.o
gcc -m32 -O0 cos.o test.c -lm -o test
(如果您不在 64 位系统上,您可以删除 --32/-m32(应该吗?)) 其中 test.c 是以下 C 源文件:
#include <stdio.h>
#include <time.h>
#define N 40000000
extern double fastcos ();
extern double slowcos ();
int main() {
int k;
double r;
clock_t t;
t = clock();
for (k = 0; k < N;k ++)
r = fastcos();
printf ("%gs\n",(double) (clock() - t) / CLOCKS_PER_SEC);
printf("fastcos = %g\n", r);
t = clock();
for (k = 0; k < N;k ++)
r = slowcos();
printf ("%gs\n",(double) (clock() - t) / CLOCKS_PER_SEC);
printf("slowcos = %g\n", r);
return 0;
}
在我的电脑上输出:
1.55687s
fastcos = -0.999999
2.29821s
slowcos = -0.999999
再说一句。如果在标题中添加“.global id”行,请将 fastcos 和 slowcos 中的“call cos”行替换为“call id”,并在C 文件。然后,您获得:
0.360433s
fastpi = 3.14
0.370393s
slowpi = 3.14
这段代码应该在函数 cos(或 id)的内部调用之外花费大约相同的时间。所以这应该表明差异发生在 cosinus 函数的执行过程中。但我不明白什么可以证明这种差异是合理的。 %esp的对齐方式没有区别。
最后,我想说的是,我在现实生活中的“数值”代码中观察到了这些差异,其中瓶颈通常是“基本数学函数”(如 cos 或 exp)的计算。此外,这两个版本都是由高级编程语言的编译器生成的。我主要关心的是了解那里发生了什么。
【问题讨论】:
-
加载存储转发。
-
嗯,很确定我的编译器会将 cos() 调用提升到循环之外,然后完全消除循环。所以你可能没有运行优化的构建。你怎么知道导致减速的不是 for() 循环?例如,分支目标不会对齐。尝试将调用交换为 slow/fastcos()。
-
@HansPassant:编译器不解析程序集,它所要做的只是函数原型。如果没有
__attribute__((const))或类似的东西,编译器就无法知道对fastcos()的每次调用都会执行相同的操作。不过,您可能会遇到功能对齐的问题。 -
4000 万次迭代,在 32 位机器上只慢 0.7 秒?这值得浪费你的脑细胞吗?
-
@e4c5 这种特殊功能的性能在实际代码中确实很重要。
标签: assembly floating-point double 32-bit x87