繁体   English   中英

案例陈述效率c

[英]case statement efficiency in c

我在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个定义到更窄(甚至连续)的范围可以加快速度。 我一直认为编译器会以一种使其数值无关的方式处理案例值,但我无法找到任何讨论来验证/使该假设无效。

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

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

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

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

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

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

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

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

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

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

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
}

阅读代码以确定switch编译的内容。

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

如果使用GCC,我会做一个快速测试,将GCC的计算goto与一个简单的排序数组相结合,这样你就可以使用好的旧二进制搜索 后者将把你的代码所做的最坏情况比较的数量从250/2减少到log 2 (250),即大约8。

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

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

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

您的问题是ID的范围不是连续的。 没有编译器可以做得更好,而不是一系列对数深度条件,这里大约8。

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

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

你可以自己实现一些东西,或者你可以尝试给编译器一些线索。 在后一种情况下,您很有可能编译器“获取它”,然后比您自己的实现更优化解决方案 - 并且编译器可以避免限制您自己的解决方案的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转换为实际上是指向数据结构的不同部分的指针值。 如果所有情况都包含对同一种指针的单个读取,那么您肯定不希望使用开关。 您需要查看数据的形状并找到更直接地计算指针的方法。 或者简单地切换类型并分别计算地址。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM