加速大型交换机和if-else

时间:2010-10-31 18:20:23

标签: c gcc

我可以做些什么来手动改善大型开关和if-elses速度?我可能需要某种哈希或查找表。

我正在使用gcc和C代码,我怀疑gcc是否有任何内置的优化。

编辑: 我的开关代码是每个开关的样子,根据特定的int是否为某个值来做某事。 我的if-elses看起来像这样:

if( !strcmp( "val1", str ) )
foo();
else if( !strcmp( "val2", str ) )
foo2();
...

我也有ifs这样做

if( struct.member1 != NULL )
foo();
if( struct.member2 != NULL )
foo2();

EDIT2: 谢谢大家。我不确定应该选哪一个作为答案,因为很多这些答案都有有效的观点和有价值的见解。不幸的是,我必须选择一个。但是,谢谢大家! 最后,使用完美的哈希表似乎是获得if和switch的访问的O(n)时间的最佳方式。

9 个答案:

答案 0 :(得分:2)

使用哈希表:

  1. 选择哈希函数。这个是个大问题。速度,散列质量和输出大小之间存在权衡。加密算法可以产生良好的散列函数。散列函数使用输入值的所有位执行一些计算,以返回一些具有较少位数的输出值。
  2. 因此哈希函数采用字符串 并返回0到0之间的整数 N。
  3. 现在,您可以在大小为N的表中查找指向函数的指针。
  4. 表中的每个条目都是链表(或其他一些可搜索的数据结构),因为有可能发生冲突,即两个字符串映射到相同的哈希值。
  5. E.g。

    lets say hash(char*) returns a value between 0 and 3.
    hash("val1") returns 2
    hash("val2") returns 0
    hash("val3") also returns 0
    hash("val4") returns 1
    

    现在您的哈希表看起来像:

    table[0] ("val2",foo2) ("val3", foo3)
    table[1] ("val4",foo4)
    table[2] ("val1",foo1)
    table[3] <empty>
    

    我希望你能看到使用哈希表进行匹配的成本是如何计算哈希函数所花费的时间以及在哈希表中搜索条目所花费的时间。如果哈希表足够大,则大多数哈希表条目的项目都很少。

答案 1 :(得分:2)

对于字符串,如果您的可能字符串数量有限,请使用perfect hash并打开结果。只有30个左右的字符串,找到一个完美的哈希应该很容易。如果您还需要验证输入,则在每种情况下都必须执行一次strcmp,但这非常便宜。

除此之外,让编译器优化您的交换机。如果你已经做了足够的测试,知道在这里度过的时间对于性能至关重要,那就更好了。

答案 2 :(得分:1)

我不确定你在寻找什么,但在this question

中讨论了使用gcc进行分支预测

答案 3 :(得分:1)

它有。只需看看生成的代码。至少它可以优化开关。

您可以使用哈希表来优化代码,但我确信GCC会为您做同样的事情。

另一件事是if-else,当它们包含一些复杂的布尔表达式时。我不会在这里回答这部分问题。

答案 4 :(得分:1)

这实际上取决于您正在使用的代码库以及是否可以进一步/更好地模块化。否则,如果没有别的我可以推荐这个。

如果有比其他情况更常见的情况(一件事或两件事发生的事情比其他情况多),请将它们放在开关/ if / else的开头,这样在更常见的情况下,您的程序只会首先进行一两次比较并短路其路径。对于任何代码恕我直言都是一个好主意。

答案 5 :(得分:1)

这在很大程度上取决于您所比较的字符串。您可以切换字符串的某些特征:

  • 如果你知道他们的不同之处 在第四位,你可以 仅在switch上执行str[3] 然后执行strcmp
  • 或者查看一些校验和和switch

但所有这些都是手工制作的,你肯定应该检查gcc产生的汇编程序。

答案 6 :(得分:1)

哈希表非常适合加速一堆字符串比较。 您可能希望查看不使用Nul终止字符串的字符串库,如C stdlib所做的那样。 C中的大量字符串操作涉及很多“通过字符串查找nul,然后执行操作”。 像SafeStr这样的字符串库保存有关字符串长度的信息,因此不需要花时间来扫描nuls,尤其是对于长度不等的字符串

答案 7 :(得分:1)

(我在之前的研究中引用了一些关于这个主题的文章) specINT2006基准测试,458.sjeng,实现 国际象棋模拟器,使用许多开关语句来处理 不同的棋子。每个陈述的形式如下:

switch (board[from]) {  
case (wpawn): ...  
case (wknight): ... 

编译器(gcc)生成的指令序列类似 以下内容:

40752b: mov -0x28(%rbp),%eax
40752e: mov 0x4238(,%rax,8),%rax
407536: jmpq *%rax

此程序集充当查找表。通过将switch ... case拆分为多个switch语句,可以进一步加快编译代码的速度。您需要将案例值连续保存,并将最常见的案例放入不同的switch语句中。这特别改善了间接分支预测。

我会将剩余的问题留给其他人。

答案 8 :(得分:1)

其他答案已经建议使用哈希表,我建议使用perfect hash function生成gperf(或最小完美哈希函数,请参阅wikipedia page以获取一些链接)