案例陈述效率c

时间:2013-07-10 06:23:14

标签: c performance switch-statement

我在Visual C中有一个大的switch语句,大约250个案例:

#define BOP -42
#define COP -823
#define MOP -5759

int getScarFieldValue(int id, int ivIndex, int rayIndex, int scarIndex, int reamIndex)
{
    int returnValue = INT_MAX;  
    switch (id)
    {       
        case BOP      : returnValue  = Scar[ivIndex][rayIndex].bop[scarIndex][reamIndex];    break;
        case COP        : returnValue  = Scar[ivIndex][rayIndex].cop[scarIndex][reamIndex];       break;
        case MOP         : returnValue  = Scar[ivIndex][rayIndex].mop[scarIndex][reamIndex];     break;
        .....
        default:  return(INT_MAX);
     }
}

你会注意到#defines有一个很大的范围,从-1到-10,000。事情是狗慢,我想知道花几个小时重新定义这250个定义到更窄(甚至连续)的范围可以加快速度。我一直认为编译器会以一种使其数值无关的方式处理案例值,但我无法找到任何讨论来验证/使该假设无效。

8 个答案:

答案 0 :(得分:2)

反汇编编译的代码并查看编译器的功能。我查看了几个不同编译器的输出,并且大型switch语句总是被编译成二进制决策树或跳转表。跳转表是您可以获得的最佳选择,如果您打开的值在较窄的范围内,则编译器更有可能生成跳转表。它还有助于在某些编译器上有一个默认语句(但在其他编译器上没有必要)。

这种情况下,反汇编是您唯一的好选择,此级别的代码生成细节很少有详细记录。

答案 1 :(得分:1)

也许你应该使用哈希表,这样你就可以搜索哈希表而不是“switch case”。

答案 2 :(得分:1)

如果查看代码的程序集输出,您可能会注意到您的switch语句正在编译成类似级联if语句的代码:

if (id == BOP) ...
else if (id == COP) ...
else if (id == MOP) ...
...
else ...

因此,加速switch语句的一个简单提示是将最常见的案例移到顶部附近。

如果案例值已排序,则编译器可能能够生成二元决策树,从而将复杂性从线性降低到对数。

在支持它的编译器上具有足够高的优化级别时,编译器可能能够生成计算的goto样式代码。对于非连续值,跳转到的偏移量将存储在哈希表中,并为案例值生成完美哈希函数。对于连续值,不需要散列函数,因为可以使用简单的索引数组来存储跳转偏移。您必须检查汇编程序输出以获取优化代码,以查看您的编译器是否支持此功能。

否则,最好在case值上创建自己的哈希,而不是使用switch,你可以自己进行哈希表查找以找到要使用的正确矩阵,然后获取你的值

答案 3 :(得分:1)

简单的解决方案: 将开关盒分成多个部分。

if(id<=50)
{
    switch(id)
    {
      //All cases between 0 to 50
    }
}
else if (id>50 && id<=100)
{
    switch(id)
    {
      //All cases between 51 to 100
}
//and so on

范围的选择是你的。并且不要创造许多范围。这将确保代码比当前代码更快。 或者,您可以使用函数指针和写入包含要在案例中执行的语句的函数。 我更喜欢这种方法。

typedef struct
{
    void(*Cur_FunctnPtr)();     
}CMDS_Functn;

void (*Cur_Func)();
CMDS_Functn FunctionArray[66] = {
                    /*00-07*/    (*Func1),(*Func2),(*Func3),...
                    /*08-0F*/       

                    /*40-47*/       };

void my_func()
{
...  //what ever code
    Cur_Func = FunctionArray[id].Cur_FunctnPtr; //Load current Function Pointer
        (*Cur_Func)();
... // what ever code
}

答案 4 :(得分:1)

阅读代码以找出switch编译的内容。

如果您有一个方便的哈希表实现,您可以尝试使用它,但它当然要求您将所有“动作”代码提取到可以从哈希表查找结果跳转到的内容。

如果使用GCC,我会快速测试GCC's computed goto和一个简单的排序数组,这样你就可以使用好的binary search。后者将把你的代码所做的最坏情况比较的数量从250/2减少到log 2 (250),即大约8。

这将需要在编译时声明的查找表(并且可能在运行时排序,一次),这在内存开销方面可能比大多数哈希表管理的更好。

答案 5 :(得分:0)

如果您知道可能的id值分布的特征,请在case语句中以最可能最不可能的顺序对它们进行测试。

如果经常调用它,您可能需要考虑将选项存储在字典中:它们在没有串行比较的情况下得到解析,因此如果真的有10,002种选择可能会节省大量时间。

答案 6 :(得分:0)

您的问题是ID的范围不是连续的。没有编译器可以做到这一点比使用对数深度的级联条件更好,这里约为8。

一种治愈这种方法的方法是使用enum来连续获取ID,然后编译器可以使用跳转表来加快速度。要真正了解这是否有用,您必须检查应用程序的其余部分,看它是否支持更改值。

答案 7 :(得分:0)

编译器只会使用它所知道的技术进行优化,如果这些技术都不起作用,那么你会得到一些可怕的东西。

你可以自己实现一些东西,或者你可以尝试给编译器一些线索。在后一种情况下,你很有可能让编译器“获得它”,然后比你自己的实现更优化解决方案 - 编译器可以避免限制你自己的解决方案的C语法限制。

至于解决方案;显然最好的是将它们重新编号为连续。

另一种方法是获取250个值并搜索完美哈希函数,将它们减少到8位数量。

#define PERFECT_HASH(x) ((x) & 0xff) /* some clever function of x */

switch (PERFECT_HASH(id))
{       
case PERFECT_HASH(BOP): returnValue  = Scar[ivIndex][rayIndex].bop[scarIndex][reamIndex];    break;
case PERFECT_HASH(COP): returnValue  = Scar[ivIndex][rayIndex].cop[scarIndex][reamIndex];       break;
case PERFECT_HASH(MOP): returnValue  = Scar[ivIndex][rayIndex].mop[scarIndex][reamIndex];     break;
.....
default:  return(INT_MAX);
}

但是,在剪切并粘贴该代码之后,看起来您正在使用switch语句将id转换为实际上是指向数据结构的不同部分的指针值。如果所有情况都包含对同一种指针的单个读取,那么您肯定不希望使用开关。您需要查看数据的形状并找到更直接地计算指针的方法。或者只是简单地切换类型并单独计算地址。