[英]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.