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

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

// v1.c #include  #include  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.c #include  #include  int main(void) { int i, j; int nbr_values = 8192; int n_iter = 100000; float x[nbr_values]; for (i = 0; i < nbr_values; ++i) { x[i] = 1; } for (i = 0; i < n_iter; i++) { for (j = 0; j < nbr_values; ++j) { x[j] = sin(x[j]); } } printf("%f\n", x[0]); return 0; } 

当我使用gcc 4.7.2和-O3 -ffast-math编译它们并在Sandy Bridge框上运行时,第二个程序的速度是第一个程序的两倍。

这是为什么?

一个可疑的是v1 i循环的连续迭代之间的数据依赖性。 但是,我不太明白完整的解释是什么。

(问题的灵感来自为什么我的python / numpy示例比纯C实现更快? )

编辑:

这是为v1生成的程序集:

  movl $8192, %ebp pushq %rbx LCFI1: subq $8, %rsp LCFI2: .align 4 L2: movl $100000, %ebx movss LC0(%rip), %xmm0 jmp L5 .align 4 L3: call _sinf L5: subl $1, %ebx jne L3 subl $1, %ebp .p2align 4,,2 jne L2 

对于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) ... 

也就是说, sin( )每次计算都不能开始,直到前一次调用的结果可用为止; 它必须等待以前的整个计算。 这意味着对于N次调用sin ,所需的总时间是单次sin评估的延迟的819200000倍。

相反,在v2 ,您执行以下操作:

 x[0] <-- sin(x[0]) x[1] <-- sin(x[1]) x[2] <-- sin(x[2]) ... 

注意每次对sin召唤都不依赖于之前的召唤。 实际上,对sin的调用都是独立的,只要必要的寄存器和ALU资源可用,处理器就可以在每个调用上开始(无需等待先前的计算完成)。 因此,所需的时间是sin函数的吞吐量的函数,而不是等待时间,因此v2可以在明显更短的时间内完成。


我还应该注意到DeadMG是正确的, v1v2在forms上是等价的,并且在完美的世界中,编译器会将它们优化为100000个sin评估的单个链(或者简单地在编译时评估结果)。 可悲的是,我们生活在一个不完美的世界。

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

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

除此之外,以不同方式存储结果,我没有看到任何差异。

但是,有所不同的是,在第二种情况下,每个循环的输入都在改变。 所以我怀疑发生的事情是,在循环中的某些时间,sin值变得更容易计算。 这可以产生很大的不同。 计算sin并不是完全无关紧要的,它是一个循环计算,直到退出条件被击中。