在计算斐波纳契数时,一种常见的方法是将数对(a, b)
映射到(b, a + b)
。通常可以通过定义第三个变量c
并进行交换来完成此操作。但是,我意识到您可以执行以下操作,避免使用第三个整数变量:
b = a + b; // b2 = a1 + b1
a = b - a; // a2 = b2 - a1 = b1, Ta-da!
我希望它比使用第三个变量更快,因为在我看来,这种新方法只需要考虑两个内存位置。
所以我写了下面的C程序来比较这些过程。这些模拟斐波那契数的计算,但是请放心,我知道由于大小限制,它们将无法计算正确的值。
(注意:我现在意识到没有必要将n
设为long int
,但是我会保持原样,因为这是我第一次编译它的方式)
// Using the 'b=a+b;a=b-a;' method.
#include <stdio.h>
int main() {
long int n = 1000000; // Number of iterations.
long int a,b;
a = 0; b = 1;
while (n--) {
b = a + b;
a = b - a;
}
printf("%lu\n", a);
}
// Using the third-variable method.
#include <stdio.h>
int main() {
long int n = 1000000; // Number of iterations.
long int a,b,c;
a = 0; b = 1;
while (n--) {
c = a;
a = b;
b = b + c;
}
printf("%lu\n", a);
}
当我在GCC上运行两者(未启用优化)时,我发现速度始终如一:
$ time ./PlusMinus
14197223477820724411
real 0m0.014s
user 0m0.009s
sys 0m0.002s
$ time ./ThirdVar
14197223477820724411
real 0m0.012s
user 0m0.008s
sys 0m0.002s
当我使用-O3
在GCC上运行两者时,程序集输出相等。 (我怀疑我在陈述一个在以前的编辑中胜过另一个时有偏见。)
检查每个组件的程序集,我发现PlusMinus.s
实际上比ThirdVar.s
少一条指令,但运行速度始终较慢。
为什么会出现这种时差?不仅如此,为什么加/减法的速度与我的预期相反?
答案 0 :(得分:7)
为什么会出现这种时差?
使用优化进行编译(在最新版本的gcc和clang下)没有时间差异。例如,用于x86_64的gcc 8.1都可以编译为:
.LC0:
.string "%lu\n"
main:
sub rsp, 8
mov eax, 1000000
mov esi, 1
mov edx, 0
jmp .L2
.L3:
mov rsi, rcx
.L2:
lea rcx, [rdx+rsi]
mov rdx, rsi
sub rax, 1
jne .L3
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
add rsp, 8
ret
不仅如此,为什么加/减法的速度与我的预期相反呢?
加法和减法可能比移动要慢。但是,在大多数体系结构(例如x86 CPU)中,它基本上是相同的(1个周期加内存延迟)。所以这不能解释它。
真正的问题很可能是数据之间的依赖关系。参见:
b = a + b;
a = b - a;
要计算第二行,您必须完成计算第一行的值。如果编译器按原样使用表达式(在-O0
下就是这种情况),那么CPU将会看到。
但是,在第二个示例中:
c = a;
a = b;
b = b + c;
您可以同时计算新的a
和b
,因为它们彼此不依赖。并且,在现代处理器中,这些操作实际上可以并行计算。或者,换句话说,您不是通过等待上一个结果来“停止”处理器。这称为Instruction-level parallelism。