我在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个定义到更窄(甚至连续)的范围可以加快速度。我一直认为编译器会以一种使其数值无关的方式处理案例值,但我无法找到任何讨论来验证/使该假设无效。
答案 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
转换为实际上是指向数据结构的不同部分的指针值。如果所有情况都包含对同一种指针的单个读取,那么您肯定不希望使用开关。您需要查看数据的形状并找到更直接地计算指针的方法。或者只是简单地切换类型并单独计算地址。