Mar*_*arc 1 floating-point double assembly 32-bit x87
这是一小段汇编代码(我使用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
Run Code Online (Sandbox Code Playgroud)
可以使用以下原型轻松地从C调用这些符号:
extern double fastcos ();
extern double slowcos ();
Run Code Online (Sandbox Code Playgroud)
它们都返回"cos(3.14)"的值,但slowcos比intel 32位架构上的fastcos慢两倍.我的问题如下:
有什么可以解释这么大的性能差异?
在linux上,您可以通过在文件调用cos.asm中复制此代码并通过调用来测试:
as --32 cos.asm -o cos.o
gcc -m32 -O0 cos.o test.c -lm -o test
Run Code Online (Sandbox Code Playgroud)
(你可以删除--32/-m32(应该?),如果你不在64位系统上),其中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;
}
Run Code Online (Sandbox Code Playgroud)
在我的电脑上它输出:
1.55687s
fastcos = -0.999999
2.29821s
slowcos = -0.999999
Run Code Online (Sandbox Code Playgroud)
再说一遍.如果在标题中添加".global id"行,则用"call id"替换fastcos和slowcos中的"call cos"行,并在其中添加以下"double id(double x){return x;}" C档.然后,您获得:
0.360433s
fastpi = 3.14
0.370393s
slowpi = 3.14
Run Code Online (Sandbox Code Playgroud)
这段代码应该在函数cos(或id)的内部调用之外花费大约相同的时间.所以这应该表明在执行cosinus函数期间发生了差异.但我不明白什么可以证明这种差异.%esp的对齐没有差异.
最后,我想说我在现实生活中的"数值"代码中观察到了这些差异,其中瓶颈通常是"基本数学函数"(如cos或exp)的计算.此外,两个版本都是由高级编程语言的编译器生成的.我主要关心的是了解那里发生的事情.
当一个现代的x86写入内存,并且不久之后再次读取相同的内存时,它会欺骗以避免对内存/缓存进行完整的往返:
英特尔®64和IA-32架构优化参考手册
2.3.4.4存储转发
如果负载跟随存储并重新加载存储写入内存的数据,则英特尔酷睿微体系结构可以将数据直接从存储转发到负载.此过程称为存储加载转发,通过使加载直接从存储操作而不是通过内存获取数据来节省周期.
文本继续讨论对齐要求,但重要的是:
商店的大小必须等于或大于正在加载的数据的大小.
在slow函数中,将8字节双精度存储在两个四字节的块中.据推测,cos()-function将其加载到一个块中,因此加载必须等到存储提交到缓存.
另一方面,在快速函数中,您存储一个8字节的块,它保留在cpu的内部缓冲区中,从中cos()可以立即满足加载.