为什么在使用浮点指令(fldl&fstpl)推送它时,使用两个32位的推送在堆栈上推双倍?

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)的计算.此外,两个版本都是由高级编程语言的编译器生成的.我主要关心的是了解那里发生的事情.

EOF*_*EOF 8

当一个现代的x86写入内存,并且不久之后再次读取相同的内存时,它会欺骗以避免对内存/缓存进行完整的往返:

英特尔®64和IA-32架构优化参考手册

2.3.4.4存储转发

如果负载跟随存储并重新加载存储写入内存的数据,则英特尔酷睿微体系结构可以将数据直接从存储转发到负载.此过程称为存储加载转发,通过使加载直接从存储操作而不是通过内存获取数据来节省周期.

文本继续讨论对齐要求,但重要的是:

商店的大小必须等于或大于正在加载的数据的大小.

在slow函数中,将8字节双精度存储在两个四字节的块中.据推测,cos()-function将其加载到一个块中,因此加载必须等到存储提交到缓存.

另一方面,在快速函数中,您存储一个8字节的块,它保留在cpu的内部缓冲区中,从中cos()可以立即满足加载.