C++ C++;功能静态,本地性能?
我有一个函数,它必须根据表示运算符类型的成员枚举的值返回true或false 我想知道以下选项中哪一个最快,因为我不确定编译器将要进行哪些隐式优化(如果有的话)C++ C++;功能静态,本地性能?,c++,performance,optimization,C++,Performance,Optimization,我有一个函数,它必须根据表示运算符类型的成员枚举的值返回true或false 我想知道以下选项中哪一个最快,因为我不确定编译器将要进行哪些隐式优化(如果有的话) inline bool isBinaryOper( void ) const // Fastest i assume. { static const bool arr[] = { true, // E_PLUS true, // E_MINUS
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
{
if ( this->_eType == E_PLUS ) return true;
else if ( this->_eType == E_MINUS ) return true;
// etc...
}
哪一个最快,为什么?使用该值作为数组的索引要比
开关
语句快得多
第二个和第三个代码块的性能大致相同。但是第一个快速获取索引并使用它访问所需的数组元素。那将是我的偏好;但是,您可能还需要添加错误检查,以确保参数在预期范围内。如果对枚举进行分区,使所有返回true的值都位于所有返回false的值之前,则可以执行以下操作:
inline bool isBinaryOper() const
{
return this->_eType < E_LPARENT;
}
inline bool isBinaryOper()常量
{
返回此->\u eType
这个问题给我的印象是过早优化的一个例子,但是为了它的价值,我会使用switch
语句,尽管它可能会稍微慢一点,因为:
default:
案例,交换机实现可以保护您免受无效数据或枚举定义更改的影响,从而简化调试
if。。。否则如果。。。否则如果…
选项,这几乎肯定是最慢的
这使您不用考虑如何对备选方案排序,特别是因为您可能需要具有不同排序的各种布尔函数。除非您是计算机体系结构方面的专家,否则您可以合理地假设您的编译器更好地理解它
我认为数组查找很可能是最有效的方法。对于优化编译器来说,根本没有“肥肉”可以修剪 当然,该表最有可能放在其他段(.rdata而不是.text)中,因此该表将占用更多缓存线。然而,你会遇到任何负面影响的机会是微不足道的 当然,编译器可能会在表查找中实现一个大小写密集的
开关
。这将大大改善“幼稚”的级联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;
}
#包括
#包括
枚举E
{
E0,
E1,
E2,
E3,
E4,
E5,
E6,
E7,
};
布尔f1(E x)
{
如果(x>E7 | | x1&&argv[1][0]='s')
?f2
:f1;
时钟t=时钟();
int r=0;
对于(int i=0;i<10000;++i)
对于(int j=0;j<100000;++j)
r+=f((E)(j&E7));
printf(“%d%I64d\n”,r,u int64(clock()-t));
返回0;
}
使用MSVC++16为x86和x64(带-O2选项)编译,f1
提供的时钟比f2
好3倍以上
通过分析目标代码,很容易看出原因:开关
确实是使用一个表实现的,但它是一个标签表。代码从表中获取一个地址,然后跳转到该地址。一个分支有效地返回0
,另一个返回1
。这不仅是一个不必要的步骤,而且还会导致频繁的分支预测失误。您是否有数据或理由支持这一说法?我不认为数组查找必然比跳转表快(跳转表是密集switch
语句的典型实现)。@jonathanwood。谢谢你的回答。对于第一个选项,我考虑了哈希表的设计,但我更关心的是静态局部变量。当使用静态局部变量初始化或任何其他隐式内容输入函数时,是否执行了测试?数组查找的典型实现只是将索引乘以每个元素的大小添加到数组的基址。不需要比较或循环。switch或if语句需要比较每个项。数组索引会快很多倍。@Virus721:访问静态数据不会影响性能。事实上,在某些情况下,它可能会更快,因为您不需要通过类指针找到它。@JonathanWood为什么切换需要比较每个项?开关设计时考虑到使用枚举作为索引的跳转表。如果这些E_uu
常量都是base-2的幂,并且可以组合成一个位掩码,则可以更快地实现跳转。然后:返回!(这个-> yype和(EyLtPrimeErrPrime..…)) @ TADMAN谢谢,我会考虑这个选项,但我不确定还有多少个操作符,但是我猜应该有足够的位。
#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;
}