第五次电源操作比switch语句执行得更快

问题描述 投票:1回答:3

我正在研究一个问题,我需要返回数字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语句,为什么会这样?

c++ switch-statement
3个回答
2
投票

它并不像你想象的那样切割和干燥。根据您的输入,可以更快。在这种情况下,如果重复查看相同的值,表查找会更快。如果查找不同的值,则乘法更快。我猜这是分支预测器,每次都以常量值执行查找工作。

benchmark results

忽略“变化”的事实要高得多的事实 - 这就是模数的成本。只需将最左边的两个相互比较,然后将两个相互比较。

实时基准链接: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


0
投票

没有什么比这更令人惊讶的了:在第一种情况下(功率计算),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

0
投票

由于分支时的管道停顿,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];
}

但由于条件范围检查,这也可能很慢。

© www.soinside.com 2019 - 2024. All rights reserved.