【发布时间】:2013-01-06 06:41:07
【问题描述】:
考虑以下两个以两种不同方式执行相同计算的程序:
// 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.c
#include <stdio.h>
#include <math.h>
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;
}
当我使用带有-O3 -ffast-math 的 gcc 4.7.2 编译它们并在 Sandy Bridge 机器上运行时,第二个程序的速度是第一个程序的两倍。
这是为什么呢?
一个怀疑是v1 中i 循环的连续迭代之间的数据依赖性。但是,我不太明白完整的解释可能是什么。
(受Why is my python/numpy example faster than pure C implementation?启发的问题)
编辑:
这是为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
【问题讨论】:
-
尝试使用
double而不是float -
@BasileStarynkevitch:
double没有区别。 -
在第二个代码中,可能是因为所有 j >=1 的所有 x[j] = sin(x[j]) 在初始 sin-calc 之后将与 x[0] 相同?,因此整个内部循环可以被丢弃并用一个单一的 sin-calc 和一个填充代替? (只是在这里猜测)。第一个循环在每次内循环迭代中计算一个与值相关的 sin,第二个 sn-p 没有这样的限制。
-
@NPE 我认为数据依赖性可能是完整的解释。是什么让你认为还有另一个原因?由于第二个版本中的数据“独立性”,处理器可以在计算前一个值的同时预加载下一个计算值,等等。我不是 CPU 架构专家,但由于今天的处理器管道很长,我会考虑这实际上解释了这种性能差异,这似乎是合理的。
-
@NPE 我相信 Stephen Canon 本人是 x86 架构方面的专家。
标签: c performance gcc floating-point x86