C ++函数静态本地,性能如何?

时间:2014-05-14 15:02:40

标签: c++ performance optimization

我有一个必须返回false的函数,具体取决于代表运算符类型的成员枚举的值。

我想知道以下选项之间最快的是什么,因为我不确定编译器将进行哪些隐式优化,如果有的话。

   inline bool isBinaryOper( void ) const // Fastest i assume.
   {
      static const bool arr[] =
      {
         true,  // E_PLUS
         true,  // E_MINUS
         true,  // E_MULTIPLY
         true,  // E_DIVIDE
         false, // E_LPARENT
         false, // E_RPARENT
         false, // E_COMMA
         false  // E_SEMICOLON
      };

      return arr[ size_t( this->_eType ) ]; // Assuming values are valid indexes.
   }

或者:

   inline bool isBinaryOper( void ) const
   {
      switch( this->_eType )
      {
         case E_PLUS      : return true;
         case E_MINUS     : return true;
         case E_MULTIPLY  : return true;
         case E_DIVIDE    : return true;
         case E_LPARENT   : return false;
         case E_RPARENT   : return false;
         case E_COMMA     : return false;
         case E_SEMICOLON : return false;
         default : ...
      };
   }

或者,我猜这与前一个非常相似:

   inline bool isBinaryOper( void ) const
   {
      if      ( this->_eType == E_PLUS  ) return true;
      else if ( this->_eType == E_MINUS ) return true;
      // etc...
   }

哪一个会最快,为什么?

4 个答案:

答案 0 :(得分:1)

这个问题让我觉得它是一个过早优化的例子,但是对于它的价值,我会选择switch语句,即使它可能稍微慢一点,因为:

  1. 你不会注意到减速。

  2. 假设您填写default:案例,交换机实现可以保护您免受无效数据或枚举定义的更改,这将简化调试。

  3. gcc和clang(以及可能还有其他优秀的编译器)都会将切换优化为二进制搜索或跳转表,具体取决于备选方案的排序方式和目标平台的精确特征。在任何一种情况下,它都不会简单地对每个可能的值进行线性检查,例如if ... else if ... else if ...选项,这几乎肯定是最慢的。

    这使您无需考虑如何订购备选方案,特别是因为您可能需要具有不同排序的各种布尔函数。除非您是计算机体系结构方面的专家,否则您可以合理地假设您的编译器更好地理解它。

答案 1 :(得分:0)

将值用作数组的索引要比switch语句快得多。

您的第二个和第三个代码块的执行大致相同。但第一个快速获取索引并使用它来访问所需的数组元素。那是我的偏好;但是,您可能还想添加错误检查以确保参数在预期范围内。

答案 2 :(得分:0)

如果你的枚举被分区,所有返回true的值都来自返回false的所有值,那么你可以这样做:

inline bool isBinaryOper() const
{
   return this->_eType < E_LPARENT;
}

答案 3 :(得分:0)

我想说阵列查找很可能是最有效的。对于要优化的编译器来说,它根本就没有“胖”。

当然,该表最有可能放在其他段(.rdata而不是.text)中,因此该表将占用更多缓存行。但是,你会遇到任何负面影响的可能性微不足道。

当然,编译器可能会将具有密集大小写值的switch实现到表查找中。这将比'naïve'级联 - if实现提供巨大的改进。但是,无法保证以最直接的方式完成。


一个非常简单的快速和肮脏的实验证实了我的推理:

#include <stdio.h>
#include <time.h>

enum E
{
    E0,
    E1,
    E2,
    E3,
    E4,
    E5,
    E6,
    E7,
};

bool f1(E x)
{
    if (x > E7 || x < E0)
        throw "ohbadbad";

    static const bool t[] =
    {
        true,
        true,
        true,
        true,
        false,
        false,
        false,
        false,
    };

    return t[x];
}

bool f2(E x)
{
    switch (x)
    {
    case E0: return true;
    case E1: return true;
    case E2: return true;
    case E3: return true;
    case E4: return false;
    case E5: return false;
    case E6: return false;
    case E7: return false;
    default: throw "ohbadbad";
    }
}

int main(int argc, char* argv[])
{
    bool (*f)(E) = (argc > 1 && argv[1][0] == 's')
        ? f2
        : f1;

    clock_t t = clock();

    int r = 0;

    for (int i = 0; i < 10000; ++i)
        for (int j = 0; j < 100000; ++j)
            r += f((E)(j & E7));

    printf("%d %I64d\n", r, __int64(clock() - t));

    return 0;
}

使用MSVC ++ 16编译x86和x64(带-O2选项),f1提供的时钟比f2好3倍以上。

分析目标代码,很容易理解为什么:switch确实是使用表实现的 - 但它是一个标签表。代码从表中获取一个地址,然后跳转到该地址。一个分支有效地return 0,另一个return 1。这不仅是一个不必要的步骤,而且还会导致频繁的分支误预测。