If-else-if与地图

时间:2010-03-17 06:56:07

标签: c++ performance map

假设我有一个if / else-if链:

if( x.GetId() == 1 )
{
}
else if( x.GetId() == 2 )
{
}
// ... 50 more else if statements

我想知道的是,如果我保留地图,在性能方面会更好吗? (假设键是整数)

7 个答案:

答案 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%肯定你在做什么,那么语法就会搞砸了,而且你的任何东西都有点过头了可行地使用地图。

基本上,我会对你要解决的问题感兴趣。你可能最好使用地图或开关盒,但如果你认为你可以使用地图,那绝对是你应该使用的。