在紧密循环中哪个更快? [swich-cast,if-else,goto-label] 第一个问题:第二个问题:

问题描述

我有一个函数f(param),它根据输入 (param) 进行一次计算。这个函数应该在一个紧密的循环中被调用大约 100 万次(最大值):

for (std::uint_fast64_t i = 0; i < 1'000'000; ++i) {
  f (param);
}

一个问题

我的(第一)问题是编写 param 函数的条件部分(根据 f() 做什么)的最有效方法是什么。我尝试了一些我知道的选项:

if-else if

static int f_3(int const param) {
  if (A::_1 == param) {
    return A::_1 * param;
  } else if (A::_2 == param) {
    return A::_2 * param;
  } // ... 
}

switch-case

static int f(int const param) {
  switch (param) {
    case A::_1:
      return param * A::_1;
    // ...
  }
}

goto-label

static int f_2(int const param) {
  void constexpr* const  _table[] = {
    && L1,// ... 
  };

  goto* _table[param];

L1:
  return A::_1 * param;
// ...
}

我对它进行了基准测试:

  • The complete code.
  • 编译器g++ (GCC) 10.2.0
  • 编译器选项-O3 -std=c++17 -lboost_timer
  • 操作系统ArchLinux 5.10.8-arch1-1
int main(int argc,char* argv[]) {
  using ufast_t = std::uint_fast64_t;

  ufast_t constexpr n = 1'000'000'000ULL;
  ufast_t sum = 0;

  {
    std::cout << "switch-case :";
    boost::timer::auto_cpu_timer _f;
    for (ufast_t i = 0; i < n; ++i) {
      sum += f(argc);
    }
  }
  std::cout << "---------------------------" << std::endl;
  {
    std::cout << "goto        :";
    boost::timer::auto_cpu_timer _f2;
    for (ufast_t i = 0; i < n; ++i) {
      sum += f_2(argc);
    }
  }
  std::cout << "---------------------------" << std::endl;
  {
    std::cout << "if-elseif   :";
    boost::timer::auto_cpu_timer _f3;
    for (ufast_t i = 0; i < n; ++i) {
      sum += f_3(argc);
    }
  }
  std::cout << std::endl;

  return sum;
}

但它有(我认为)一个奇怪的输出

 > ./a.out 1 2 3 4 5 6
switch-case : 1.056976s wall,1.050000s user + 0.000000s system = 1.050000s cpu (99.3%)
---------------------------
goto        : 0.000001s wall,0.000000s user + 0.000000s system = 0.000000s cpu (n/a%)
---------------------------
if-elseif   : 0.645751s wall,0.640000s user + 0.000000s system = 0.640000s cpu (99.1%)

 > echo $?
0

那么有什么(有效的)方法可以做到这一点吗?或者我手头只有这些选项,我必须根据一些基准选择其中之一?

第二个问题

为什么上述基准中的 goto 没有任何开销?这是未定义的行为代码?或者编译器在优化阶段以某种方式删除了它们的循环?

解决方法

我不认为这 3 种不同的方法应该彼此有很大不同,因为编译器可能会识别您在做什么并做最好的事情。最易读的变体是 switch,但这是基于意见的。它也应该作为汇编程序发出一个跳转表,这应该是最快的选择。但只要相信编译器就行了。

最好将支票移出循环。我可以想象编译器将能够识别出 param 不会改变并移动检出本身,但这不太确定。为了确保,您可以执行以下操作:

template<A param>
auto foo() {
    // This function will be generated once for each enumerator of A that is included
    // in the switch below. In each variant the runtime code will not contain these
    // checks against param.
    if constexpr(param == A::_1) { 
    } else if constexpr(param == A::_2) {
    } ...
}

template<A param>
auto loop() {
    for(auto i = 0ul; i < 1'000'000; ++i) {
        foo<param>();
    }
}

int main(int argc,char** argv) {
    switch(argc) {
        case A::_1: 
            loop<A::_1>();
            break; 
        ....
    }
}

遗憾的是,据我所知,没有打开 argc 的好方法。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...