c++:哪个更快——在hashmap或switch语句中查找



我有一个将一个整数转换为另一个整数的代码模式。就像这样:

int t(int value) {
    switch (value) {
        case 1: return const_1;
        case 3: return const_2;
        case 4: return const_3;
        case 8: return const_4;
        default: return 0;
    }
}

目前大约有50个条目,可能以后会有更多,但可能不会超过100或200个。所有的值都是预定义的,当然我可以根据它们的值对大小写标签排序。问题是,哪种方法更快——这种方法还是将其放入哈希映射(我无法访问std::map,所以我说的是SDK中可用的自定义哈希映射)并在该表中执行查找?也许这是一个过早的优化,虽然……但我需要你的意见。

提前感谢。

EDIT:我的case值将在0到0xffff的范围内。在哈希映射的可读性方面。我不确定它是否真的具有更好的可读性,因为我仍然需要用值填充它,所以常量映射表仍然需要在我的代码中的某个地方。

EDIT-2:已经给出了很多有用的答案,非常感谢。我想在这里补充一些信息。我的哈希键是整型,而整型的哈希函数基本上就是一个带有整数溢出的乘法

EXPORT_C __NAKED__ unsigned int DefaultHash::Integer(const int& /*aInt*/)
{
_asm mov edx, [esp+4]
_asm mov eax, 9E3779B9h
_asm mul dword ptr [edx]
_asm ret
}

应该很快。

switch结构更快(或者至少不慢)。

这主要是因为switch结构将静态数据提供给编译器,而像哈希映射这样的运行时结构则不会。

如果可能的话,编译器应该将switch结构编译成代码指针数组:数组的每一项(由索引索引)指向相关代码。在运行时,这需要O(1),而散列映射可能需要更多:通常情况下,平均情况为O(log n),最坏情况为O(n),并且无论如何都是一个更大的常量内存访问。

我将添加我的5美分:

对于大约50个条目的数量,std::unordered_map(基于散列的,O(1))通常比std::map(基于树的O(ln(N)))慢,并且它们都比boost::flat_map(排序向量O(ln(N))慢,我倾向于在这种情况下使用。并不总是可以将switch编译为跳转表,如果可以,您可以简单地将您的值(或函数)放入vector中并通过索引进行访问。否则switch比boost::flat_map略快。

请注意开头的"典型"一词,如果您确实关心这段代码的性能,请执行分析(并与我们共享结果:))。

switch statement将比在散列映射中查找更快。

但是,如果您更改映射,则映射将产生更可读的代码。通过从文件中读取结果,可以很容易地使用映射实现这一点。在switch语句中,您必须更改代码并重新编译。

切换会更快。如果是少量的情况,就像您的示例一样,它将使用If链。如果有大量的情况,并且它们相当紧凑,则可以选择生成一个跳转表,这只需要几个指令。(顺便说一句,你不必订购箱子。)哈希映射是0(1),但可能需要10-40条指令。

根据定义,数组将具有最快的访问时间。

switch语句比较值,然后使用跳转表(这是一个函数指针数组)。

hashmap从数据中计算一个哈希值,然后在内存中搜索树,或者使用该哈希值作为数组的索引。慢是因为计算哈希值。

在大多数现代平台上,64k不是很大的数据量,可以作为常量数组静态分配。

数组技术的一个问题是考虑到您没有考虑到的键。一个例子是使用唯一的哨兵值。当返回值时,您知道您有一个未知键。

我建议使用static const数组的值。

哈希映射的速度取决于两个因素:哈希函数的速度和碰撞的数量。当所有的值都提前已知时,就有可能创建一个没有冲突的完美哈希函数。如果你能生成一个只包含几个算术运算的完美哈希函数,它可能会比切换更快。

我同意使用数组,但我没有投票赞成它的声誉。它只有65536个条目,所以除非你有严重的内存限制和/或你要返回一些非常大的东西,而不是像你的例子那样的int,使用静态const数组会好得多。拥有一个64k的int型数组通常只有256kB,如果可以使用short或char类型,那么它的大小将是这个大小的一半或1/4。我认为最好的你可以希望与switch语句是一个条件分支的值在它的代码指针数组和第二个条件分支跳转到数组内的值的代码。能够执行"return my_array[value]"只会导致内存取出(可能来自l3缓存)。

为了可读性,您可以将数组放在自己的文件中,并将所有值排列在网格中,每行有10或16个条目。然后用每个条目编号的第一部分注释每行(例如:"//0x12A?"),并具有定期注释行,这些注释行将与列对齐,以填充条目编号的最后一位数字(例如:"//0 1 2 3 4 5 6 7 8 9 a b c d e f ")。我已经对几个包含256个条目的数组这样做了,这比管理switch语句要容易得多。我还使用具有64k项的数组来快速计算整数对数,这变得更复杂,但我能够编写一个程序来生成所有数组代码。

对于这么大的东西,在处理更多条目之前,代码管理可能不会更容易,但这取决于您的编辑器和使用它的技能。维护这样的数组只是调整图表中的一个点,而不是查找可能存在也可能不存在于"case 1: return const_1;"长的列表中的值。一对for循环应该足以生成一个包含64k个条目的数组,这些条目被正确注释并填充为默认值。

为了访问安全,可以考虑使用某种边界检查。这可以通过boost的前提条件来实现,如果数字超出界限,抛出异常或特殊返回,或者简单地"返回my_array[value&0xffff]"。但是,您可能对您的传入值有足够强的保证,您不需要任何传入值。

我认为哪一个更快并不明显。您可能需要对这两种方法进行分析。

哈希映射的复杂度应为0(1)。

开关(如您的键不连续)可以优化为二进制搜索(至少使用GCC),其复杂度为O(log n)。

另一方面,在哈希映射上执行的任何操作都比在switch上执行的操作要昂贵得多。

不考虑碰撞时哈希表时间复杂度一般为0(1)。c++标准没有规定switch是如何实现的,但它可以实现为跳转表,时间复杂度为O(1),也可以实现为二进制搜索,时间复杂度为O(log n)或组合,取决于有多少case语句等。

总而言之,像你这样的小规模,切换更快,但哈希表可能会在大规模中胜出

最新更新