C++:啥更快 - 在 hashmap 或 switch 语句中查找?

Posted

技术标签:

【中文标题】C++:啥更快 - 在 hashmap 或 switch 语句中查找?【英文标题】:C++: What is faster - lookup in hashmap or switch statement?C++:什么更快 - 在 hashmap 或 switch 语句中查找? 【发布时间】:2011-10-15 04:31:25 【问题描述】:

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

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 个条目,以后可能会更多,但可能不会超过一百或两个。所有的值都是预定义的,当然我可以按它们的值排序 case 标签。所以问题是,什么会更快 - 这种方法或将其放入哈希映射(我无法访问 std::map,所以我说的是我的 SDK 中可用的自定义哈希映射)并在该表中执行查找?不过,也许这有点过早的优化......但我只需要你的意见。

提前致谢。

编辑:我的案例值将在 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

所以应该很快。

【问题讨论】:

你不应该真的从空虚中返回,是的,这是过早的优化,但我个人会选择好的哈希图(出于可重复性的原因) (1) std::map 不是哈希图(来自 C++11 的 std::unordered_map 是)。 (2) 我们应该如何判断自定义哈希映射的质量?它可能完全是垃圾,也可能非常出色。 顺便说一句,也许考虑一个带有命名对的enum而不是switch?这将完全摆脱函数调用。 @Haspemulator 和默认值呢? @delnan:是的,我知道您无法判断自定义地图的质量。但是让我们假设它是一个好的哈希映射。 【参考方案1】:

我会加我的 5 美分:

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

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

【讨论】:

与往常一样,要找到好的答案,请查看最底部的回复。【参考方案2】:

我同意使用数组,但我没有投票支持它的声誉。它只有 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 ABCDEF”)。我已经为几个包含 256 个条目的数组完成了此操作,这比 switch 语句更容易管理。我还使用了具有 64k 条目的数组来进行快速整数对数,这使得管理起来更加复杂,但我能够编写一个程序来生成所有数组代码。

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

为了访问安全,您可以考虑使用某种边界检查。这可以通过 boost 的先决条件来完成,如果数字超出范围,则抛出异常或特殊返回,或者简单的“return my_array[value&0xffff]”。但是,您可能对您的传入值有足够强的保证,您不需要任何它。

【讨论】:

【参考方案3】:

哈希映射的速度取决于两件事:哈希函数的速度和碰撞次数。当所有值都提前知道时,可以创建一个没有冲突的perfect hash function。如果你能生成一个只包含几个算术运算的完美哈希函数,它可能会比 switch 更快。

【讨论】:

【参考方案4】:

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

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

hashmap 从数据中计算哈希值,然后在内存中搜索树或使用哈希值作为数组的索引。由于计算散列值,速度慢。

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

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

我建议使用 static const 值数组。

【讨论】:

【参考方案5】:

在不考虑碰撞的情况下,哈希表时间复杂度一般为 O(1)。 C++ 标准没有指定 switch 是如何实现的,但它可以实现为时间复杂度为 O(1) 的跳转表,也可以实现为时间复杂度为 O(log n) 的二分查找或组合,具体取决于有多少case语句等。

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

【讨论】:

【参考方案6】:

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

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

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

另一方面,在哈希映射上执行的任何操作都将比在交换机中执行的操作昂贵得多。

【讨论】:

【参考方案7】:

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

这主要是因为switch 构造为编译器提供了静态数据,而像哈希映射这样的运行时结构却没有。

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

【讨论】:

O(n) 中的哈希映射是一个非常糟糕的哈希映射 我的教育是否有问题,我认为散列应该是O(1),在最坏的情况下(很多冲突)O(N)?我还要说开关的速度取决于您要区分的值。如果它们是具有固定步长的封闭范围,则开关速度很快。但是像 [1,100000000] 这样带有随机条目的范围呢? 是的,抱歉,已修复。 O(n) 是最坏的情况(或者对于树形图,它可能总是 O(log n))。无论如何,我的观点应该是:在某些情况下它可能需要超过 O(1),即使它是 O(1),它也很可能需要更多的常量操作。 @Nobody: switch 在可能的情况下被实现为数组,在您的示例中不是,但由于该数据对编译器静态可用,因此它应该能够给出不差的结果任何运行时结构。 不要 switch 语句使用二进制搜索,这将是 O(log(n)) 复杂度,而不是 O(1) 的哈希图?显然,哈希函数也有开销,但对于纯粹的时间复杂度来说,这似乎是多余的。【参考方案8】:

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

【讨论】:

关于案例的顺序:我在某处读到我应该这样做以实现更高的执行速度。那你说有错吗?你能提供更多的细节吗? @Hasp - If 代码变成了一个 if 链,第一种情况可能会稍微快一点。如果编译器决定使用跳转表,则无济于事。 @Haspemulator:Bo 基本上是对的。您可能有很多稀疏的情况(如 1000000、2000000、3000000 等),导致编译器生成长 if 链。那么在最坏的情况下,您的程序可能不得不一直运行到最后一个。【参考方案9】:

switch statement 比在哈希映射中查找要快。

但是,如果您更改了映射,映射将产生更易读的代码。您可以通过从文件中读取结果来轻松地使用地图执行此操作。在 switch 语句中,您必须更改代码并重新编译。

【讨论】:

以上是关于C++:啥更快 - 在 hashmap 或 switch 语句中查找?的主要内容,如果未能解决你的问题,请参考以下文章

java中谁更快hashmap.get或treemap.get [重复]

C++ Map中的数据存储方式是啥?Map和HashMap有啥区别?

hashCode() 在 HashSet 或 HashMap 中扮演啥角色 [重复]

如果我想要事件驱动服务器,该使用啥 libevent 或 libev?

将 SWI-Prolog 连接到 C++ 的问题

当对象 Hashcode 更改时,Hashmap 或 Hashset 中的查找会发生啥