C++ 第五次电源操作的执行速度比开关语句快

C++ 第五次电源操作的执行速度比开关语句快,c++,switch-statement,C++,Switch Statement,我正在研究一个问题,需要返回数字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: re

我正在研究一个问题,需要返回数字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;}

但我意识到,尽管第一个函数需要5次乘法运算,而第二个只调用一个switch语句,但使用第一个函数的程序运行速度比使用第二个函数快20%,这是为什么呢?

毫不奇怪:在第一种情况下(功率计算),
一个
变量,其结果将在后续乘法之间存储在处理器缓存中,因此,如果使用第一个选项,则只会发生一次(远)内存读取。请注意,内存访问可能比乘法本身慢十几倍

如果使用
开关
,则需要一个内存读取加上一个到标签的控件跳转(通常是可执行代码的另一个内存读取)。因此,这种方法需要更多的运行时间

补充装配代码示例(见下文)

使用乘法:

    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

它不像你想象的那样干瘪。根据您的输入,两者都可以更快。在这种情况下,如果重复查找相同的值,则查找表的速度会更快。如果查找不同的值,则乘法速度更快。我猜这是分支预测器,每次都用一个常量值执行查找

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

实时基准链接:

正在进行基准测试的生成的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);

编辑:在这两种情况下,稍微不同的基准的查找速度更快:

由于分支时管道暂停,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];
}
intpow(inta){
静态常数int-pows[10]={0,1,32,243,1024,3125,7776,16807,32768,59049};
如果(a<0 | | a>9)返回0;
返回战俘[a];
}

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

您是否查看了生成的代码?因为在特定计算机上特定编译器上使用特定编译器标志编译的特定代码生成的代码较慢。你没有给我们提供足够的信息来说明更多的事情。如果您提供了上述所有信息,那么可能有人会指出导致速度差异的代码中的显式差异,但直到那时,因为乘法恰好比switch语句快20%左右?速度不是基于语句的数量。这是一个真正的瓶颈吗?也可能是由于分支预测失败?快速查找表通常使用数组构建,而不是
开关
。请注意,
开关
有效地执行范围检查,您需要自己编写一个数组进行查找。推测生成的代码是危险的。最好让提出问题的人提供足够的代码,从实际数据中重现问题和答案。@xaxxon,这是一种微体系结构优化。您无法在生成的代码中看到缓存,您将看到对同一逻辑地址的5次访问,与C代码中的访问完全相同。即使您是正确的,最好让用户提供他们的代码,这样您就可以确保他们没有做影响其计时的其他事情(或者他们的计时是正确的)而且它们不是使用非感官编译器标志编译的。如果没有其他问题,如果你想回答这个问题,你应该为它编写示例代码,这样你就可以在答案中指向实际生成的代码来进行备份。@xaxxon我不知道你在说什么。他提供了自己的代码。@EJP汇编代码可能是指-我添加了一个使用gccMy基准生成的代码,但此版本包含为“pow3”: