C 涉及sin()的两个非常相似的函数表现出截然不同的性能——为什么?

C 涉及sin()的两个非常相似的函数表现出截然不同的性能——为什么?,c,performance,gcc,floating-point,x86,C,Performance,Gcc,Floating Point,X86,考虑以下两个程序,它们以两种不同的方式执行相同的计算: // v1.c #include <stdio.h> #include <math.h> int main(void) { int i, j; int nbr_values = 8192; int n_iter = 100000; float x; for (j = 0; j < nbr_values; j++) { x = 1; for (i = 0; i

考虑以下两个程序,它们以两种不同的方式执行相同的计算:

// v1.c
#include <stdio.h>
#include <math.h>
int main(void) {
   int i, j;
   int nbr_values = 8192;
   int n_iter = 100000;
   float x;
   for (j = 0; j < nbr_values; j++) {
      x = 1;
      for (i = 0; i < n_iter; i++)
         x = sin(x);
   }
   printf("%f\n", x);
   return 0;
}
对于
v2

        movl    $100000, %r14d
        .align 4
L8:
        xorl    %ebx, %ebx
        .align 4
L9:
        movss   (%r12,%rbx), %xmm0
        call    _sinf
        movss   %xmm0, (%r12,%rbx)
        addq    $4, %rbx
        cmpq    $32768, %rbx
        jne     L9
        subl    $1, %r14d
        jne     L8

同时忽略循环结构,只考虑调用
sin
的顺序<代码>v1执行以下操作:

x <-- sin(x)
x <-- sin(x)
x <-- sin(x)
...
x[0] <-- sin(x[0])
x[1] <-- sin(x[1])
x[2] <-- sin(x[2])
...
请注意,对
sin
的每个调用都不依赖于上一个调用。实际上,对
sin
的调用都是独立的,处理器可以在必要的寄存器和ALU资源可用时(无需等待上一次计算完成)立即开始对每一个进行调用。因此,所需时间是sin函数吞吐量的函数,而不是延迟,因此
v2
可以在更短的时间内完成



我还应该注意到,DeadMG是正确的,即
v1
v2
在形式上是等价的,在一个完美的世界中,编译器会将它们优化为一个包含100000次
sin
求值的单链(或者在编译时简单地求值)。不幸的是,我们生活在一个不完美的世界。

在第一个例子中,它运行100000个罪恶循环,8192次

在第二个示例中,它运行8192个sin循环,100000次

除此之外,以不同的方式存储结果,我看不出有任何区别


然而,在第二种情况下,每个循环的输入都在改变,这才是区别所在。所以我怀疑,在循环中的特定时间,sin值会变得更容易计算。这会带来很大的不同。计算sin并不完全是一件小事,它是一系列循环计算,直到达到退出条件为止

尝试使用
double
而不是
float
@BasileStarynkevitch:
double
没有任何区别。在第二个代码中,可能是因为所有j>=1的所有x[j]=sin(x[j])在初始sin calc之后都将与x[0]相同吗?因此,整个内部循环可以抛出并替换为一个sin calc和一个fill?(这里只是猜测)。第一个循环在每次内部循环迭代中计算一个依赖于值的sin,第二个代码段没有这样的限制。@NPE我认为数据依赖性可能是完整的解释。你怎么会认为还有别的原因?由于第二个版本中的数据“独立性”,处理器可以在计算前一个值的同时预加载下一个值进行计算,等等。我不擅长CPU架构,但由于今天的处理器管道相当长,我认为这是合理的,这实际上说明了性能差异。@ NPE我相信Stephen Canon自己是x86架构的专家。
x
的值在v1中是一个循环携带的依赖项。@StephenCanon:OSX Libm是否仍在使用我的
sin
实现?@EricPostpischil:确实如此。更重要的是,FSIN通常给出错误的结果。它不能进行参数缩减。@R..:是的,也是这样;我把“use FSIN”作为“use FPREM1+FSIN”的简写(虽然不能无限减少pi,但至少可以做些什么)。为什么要进行否决票?我真的不喜欢没有对否决票的评论的情况下进行否决票-我想知道错在哪里…我没有否决票,但值得注意的是,两个程序计算的值完全相同,只是顺序不同。
x <-- sin(x)
x <-- sin(x)
x <-- sin(x)
...
x[0] <-- sin(x[0])
x[1] <-- sin(x[1])
x[2] <-- sin(x[2])
...