我正在研究一个问题,我需要返回数字0-9的五次幂,我以为我可以通过切换加速程序
int pow(int a){return a*a*a*a*a;}
出去
int pow(int a){
switch(a){
case 0: return 0; break;
case 1: return 1; break;
case 2: return 32; break;
case 3: return 243; break;
case 4: return 1024; break;
case 5: return 3125; break;
case 6: return 7776; break;
case 7: return 16807; break;
case 8: return 32768; break;
case 9: return 59049; break;}
return 0;}
但是我意识到第一个函数的程序比第二个函数运行速度快20%,尽管第一个需要5个乘法运算而第二个函数只调用一个switch语句,为什么会这样?
它并不像你想象的那样切割和干燥。根据您的输入,可以更快。在这种情况下,如果重复查看相同的值,表查找会更快。如果查找不同的值,则乘法更快。我猜这是分支预测器,每次都以常量值执行查找工作。
忽略“变化”的事实要高得多的事实 - 这就是模数的成本。只需将最左边的两个相互比较,然后将两个相互比较。
实时基准链接:http://quick-bench.com/uZLVxVIMxE21JTsHWJVN8Is-37I
生成的基准ASM显示在右下角的该链接中。
int pow(int a){return a*a*a*a*a;}
int pow2(int a){
switch(a){
case 0: return 0; break;
case 1: return 1; break;
case 2: return 32; break;
case 3: return 243; break;
case 4: return 1024; break;
case 5: return 3125; break;
case 6: return 7776; break;
case 7: return 16807; break;
case 8: return 32768; break;
case 9: return 59049; break;}
return 0;}
static void multiply_varying(benchmark::State& state) {
// Code inside this loop is measured repeatedly
volatile int i = 0;
for (auto _ : state) {
i = (i + 1) % 9;
benchmark::DoNotOptimize(pow(i));
}
}
// Register the function as a benchmark
BENCHMARK(multiply_varying);
static void lookup_varying(benchmark::State& state) {
volatile int i = 5;
for (auto _ : state) {
i = (i + 1) % 9;
benchmark::DoNotOptimize(pow2(i));
}
}
BENCHMARK(lookup_varying);
static void multiply_constant(benchmark::State& state) {
// Code inside this loop is measured repeatedly
volatile int i = 5;
for (auto _ : state) {
benchmark::DoNotOptimize(pow(i));
}
}
// Register the function as a benchmark
BENCHMARK(multiply_constant);
static void lookup_constant(benchmark::State& state) {
volatile int i = 5;
for (auto _ : state) {
benchmark::DoNotOptimize(pow2(i));
}
}
BENCHMARK(lookup_constant);
编辑:略有不同的基准测试在两种情况下查找都更快:http://quick-bench.com/NRdzldykfQ8cQmGEn33FG0LMr2Q
没有什么比这更令人惊讶的了:在第一种情况下(功率计算),a
变量和结果将在后续乘法之间存储在处理器缓存中,因此如果使用第一个选项,则只会发生一次(远)内存读取。请注意,内存访问可能比乘法本身慢十几倍。
如果你使用switch
,你需要一个内存读取加上一个控件跳转到一个标签(这通常是可执行代码的另一个内存读取)。所以这种方式需要更多的运行时间。
附:添加汇编代码示例(见下文)
乘法:
movl 16(%rbp), %eax
imull 16(%rbp), %eax
imull 16(%rbp), %eax
imull 16(%rbp), %eax
imull 16(%rbp), %eax
使用开关 - >跳转到计算地址
movl 16(%rbp), %eax
leaq 0(,%rax,4), %rdx
leaq .L4(%rip), %rax
movl (%rdx,%rax), %eax
movslq %eax, %rdx
leaq .L4(%rip), %rax
addq %rdx, %rax
jmp *%rax
由于分支时的管道停顿,switch语句可能会更慢。
使用表查找比使用switch语句更常见,如下所示:
int pow(int a){
static const int pows[10] = { 0, 1, 32, 243, 1024, 3125, 7776, 16807, 32768, 59049 };
if (a < 0 || a > 9) return 0;
return pows[a];
}
但由于条件范围检查,这也可能很慢。