假设我有一个if / else-if链:
if( x.GetId() == 1 )
{
}
else if( x.GetId() == 2 )
{
}
// ... 50 more else if statements
我想知道的是,如果我保留地图,在性能方面会更好吗? (假设键是整数)
答案 0 :(得分:10)
地图(通常)是使用红黑树实现的,当树不断保持平衡时,它会提供O(log N)查找。你的if语句的线性列表将是O(N)最坏的情况。所以,是的,地图的查找速度会快得多。
许多人建议使用switch语句,这可能不会对您更快,具体取决于您的实际if语句。编译器有时可以通过使用跳转表来优化切换,跳转表是O(1),但这仅适用于未定义标准的值;因此,这种行为可能有点不确定。虽然有一篇很棒的文章,其中有一些关于优化switch语句的提示Optimizing C and C++ Code。
你在技术上甚至可以手动制定一个平衡的树,这最适合静态数据,我刚刚创建了一个函数来快速找到一个字节中设置的位(这是在I /上的嵌入式应用程序中使用的) O引脚中断,必须快速,99%的时间只在字节中设置1位):
unsigned char single_bit_index(unsigned char bit) {
// Hard-coded balanced tree lookup
if(bit > 0x08)
if(bit > 0x20)
if(bit == 0x40)
return 6;
else
return 7;
else
if(bit == 0x10)
return 4;
else
return 5;
else
if(bit > 0x02)
if(bit == 0x04)
return 2;
else
return 3;
else
if(bit == 0x01)
return 0;
else
return 1;
}
这为8个值中的任何一个提供了3个步骤的常量查找,这给了我非常确定的性能,线性搜索 - 给定的随机数据 - 将平均4步查找,最佳情况为1和最差 - 8个案例。
这是编译器可能不会优化到跳转表的范围的一个很好的例子,因为我搜索的8个值相距很远:1,2,4,8,16,32,64和128.它必须创建一个非常稀疏的128位置表,其中只有8个元素包含一个目标,在具有大量RAM的PC上可能不是什么大问题,但在微控制器上它可能是杀手锏。
答案 1 :(得分:5)
为什么不使用开关?
swich(x.GetId())
{
case 1: /* do work */ break; // From the most used case
case 2: /* do work */ break;
case ...: // To the less used case
}
修改强>
将最常用的案例放在交换机的顶部(如果x.GetId
通常等于50,则会出现性能问题。)
答案 2 :(得分:2)
switch
是我认为最好的事情
答案 3 :(得分:0)
更好的解决方案是switch
声明。这样您就可以只检查一次x.GetId()
的值,而不是(平均值)检查代码现在执行的次数25次。
如果你想获得幻想,你可以使用一个数据结构,其中包含指向函数的指针,这些函数可以处理大括号中的任何内容。如果您的ID值是连续的(即1到50之间的数字),那么函数指针数组将是最佳的。如果它们分散开来,那么地图会更合适。
答案 4 :(得分:0)
与大多数与绩效相关的问题一样,答案可能是。
如果ID位于幸运范围内,switch
可能会成为跳转表,为所有ID提供恒定时间查找。除了重新设计之外,你不会比这更好。或者,如果ID是连续的,但您没有从编译器中获取跳转表,则可以通过使用函数指针填充数组来强制解决此问题。
[从此处开始,switch
指的是通用的if/else
链]
map
为任何给定的ID提供最坏情况的对数查找,而switch
只能保证线性。但是,如果ID不是随机的,则按使用情况对switch
个案例进行排序可能会确保最糟糕的情况非常罕见,这无关紧要。
加载ID并将它们与函数关联时,map
会产生一些初始开销,然后每次访问ID时都会产生调用函数指针的开销。编写例程时switch
会产生额外的开销,调试时会产生很大的开销。
重新设计可能会让您一起避免这个问题。无论你如何实现它,这都闻起来像麻烦。我不禁想到有更好的方法来解决这个问题。
答案 5 :(得分:0)
如果我确实有五十种可能性的潜在开关,我肯定会想到一个指向函数的指针向量。
#include <cstdio>
#include <cstdlib>
#include <ctime>
const unsigned int Max = 4;
void f1();
void f2();
void f3();
void f4();
void (*vp[Max])();
int main()
{
vp[ 0 ] = f1;
vp[ 1 ] = f2;
vp[ 2 ] = f3;
vp[ 3 ] = f4;
srand( std::time( NULL ) );
vp[( rand() % Max )]();
}
void f1()
{
std::printf( "Hello from f1!\n" );
}
void f2()
{
std::printf( "Hello from f2!\n" );
}
void f3()
{
std::printf( "Hello from f3!\n" );
}
void f4()
{
std::printf( "Hello from f4!\n" );
}
答案 6 :(得分:0)
有许多涉及开关案例的建议。就效率而言,这可能更好,可能是相同的。不会更糟。
但是,如果您只是根据ID设置/返回值或名称,则为YES。地图正是您所需要的。 STL容器已经过优化,如果您认为可以更好地进行优化,那么您要么非常聪明,要么非常愚蠢。
例如使用名为mymap的std :: map进行单次调用,
thisvar = mymap[x.getID()];
远远好于其中的50个
if(x.getID() == ...){thisvar = ...;}
因为ID越多,效率越高。如果您对原因感兴趣,请在数据结构上寻找一个好的入门书。
但我真正关注的是维护/修理时间。如果您需要更改变量的名称,或者使用getID()或getName()进行更改,或进行任何微小的更改,您必须在示例中执行FIFTY TIMES。每次添加ID时都需要换行。
地图减少了一个代码更改无论您拥有多少ID。
也就是说,如果您实际上对每个ID执行不同的操作,那么交换机箱可能会更好。使用switch-case而不是if语句,可以提高性能和可读性。见这里:Advantage of switch over if-else statement 除非你非常清楚他们如何改进你的代码,否则我会避免指向函数的指针,因为如果你不是100%肯定你在做什么,那么语法就会搞砸了,而且你的任何东西都有点过头了可行地使用地图。
基本上,我会对你要解决的问题感兴趣。你可能最好使用地图或开关盒,但如果你认为你可以使用地图,那绝对是你应该使用的。