我有一个必须返回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...
}
哪一个会最快,为什么?
答案 0 :(得分:1)
这个问题让我觉得它是一个过早优化的例子,但是对于它的价值,我会选择switch
语句,即使它可能稍微慢一点,因为:
你不会注意到减速。
假设您填写default:
案例,交换机实现可以保护您免受无效数据或枚举定义的更改,这将简化调试。
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
。这不仅是一个不必要的步骤,而且还会导致频繁的分支误预测。