投稿 @M4n5ter,分享有趣的事!
例如,假设我们有一个 128 位的 SIMD 寄存器,可以同时存储 16 个字节(即 16 个桶的 ctrl 字节)。我们可以使用一条 SIMD 指令来比较这 16 个 ctrl 字节和目标键的 H2 哈希值的低 7 位,并得到一个 16 位的位掩码 (Bitmask),其中每一位表示对应桶是否匹配。
通过位掩码,我们可以快速地知道哪些桶可能包含匹配的键,然后只需要对这些桶进行完整的键比较。这大大减少了需要比较的桶的数量,提高了查找效率。
Rust 的 std::collections::HashMap 中使用了类似的 SIMD 优化,但具体实现细节可能会有所不同,并且会根据不同的 CPU 架构进行调整。
6. Rust HashMap 的补充说明
Rust 的 HashMap 在实现上与 Swiss Table 非常相似,但也有一些自己的特点:
安全性: Rust 非常注重内存安全,因此 HashMap 在实现时会进行各种边界检查和安全性检查,以防止内存错误。
泛型: HashMap 是泛型的,可以存储任意类型的键和值。
哈希函数: Rust 默认使用 SipHash 1-3 哈希算法,因为Rust更注重安全性。
迭代器: HashMap 提供了多种迭代器,可以方便地遍历键值对。
Entry API: HashMap 提供了一个 Entry API,可以更方便地进行插入、更新和删除操作。
总结
Swiss Table 是一种高效的哈希表实现,它通过 SIMD 指令、元数据分离、组的概念等优化策略,解决了传统哈希表的一些问题,提高了查找、插入和删除操作的性能。Rust 的 HashMap 在很大程度上借鉴了 Swiss Table 的思想,并在此基础上进行了改进和扩展,使其更安全、更易用。
来自 Gemini 2.0 Pro Experimental 02-05
好的,没问题。我会尽力详细地解释 Google Swiss Table 的工作原理,以及 Rust 中 HashMap 对它的实现。由于信息量很大,我会分几个部分来讲解,并尽量结合你的 YouTube 视频链接(虽然视频本身讲解得比较概括)和 Rust 文档中的信息。
1. Swiss Table 概述:从传统哈希表说起
在深入 Swiss Table 之前,我们需要先了解传统哈希表的一些基本概念和常见问题:
哈希函数 (Hash Function): 将任意大小的键(Key)映射到一个固定大小的整数(哈希值)。好的哈希函数应该尽量减少冲突(不同的键映射到同一个哈希值)。
哈希表 (Hash Table): 一个数组,数组的索引就是哈希值。每个索引位置通常称为一个“桶”(Bucket)。
冲突解决 (Collision Resolution): 当不同的键产生相同的哈希值时,就需要解决冲突。常见的方法有:
链地址法 (Separate Chaining): 每个桶存储一个链表,所有哈希到同一个桶的键值对都放在这个链表中。
开放地址法 (Open Addressing): 当发生冲突时,尝试在哈希表中找到另一个空闲的桶来存储。常见的探测方法有:
线性探测 (Linear Probing): 依次检查下一个桶,直到找到空桶。
二次探测 (Quadratic Probing): 按照二次方的序列来探测(例如,+1, +4, +9, ...)。
双重哈希 (Double Hashing): 使用第二个哈希函数来计算探测的步长。
传统哈希表存在的一些问题:
链地址法:
如果链表过长,查找效率会下降到 O(n),其中 n 是链表长度。
链表节点需要额外的内存来存储指针,空间利用率不高。
对 CPU 缓存不友好,因为链表节点在内存中可能不连续。
开放地址法:
聚集 (Clustering): 线性探测和二次探测容易导致“聚集”现象,即连续的桶被占用,导致后续的插入和查找效率降低。
删除操作复杂: 删除一个元素后,不能简单地将桶置为空,因为这可能会中断后续的探测序列。通常需要使用“墓碑”(Tombstone)标记。
装载因子 (Load Factor) 敏感: 装载因子(已用桶的数量 / 总桶的数量)过高时,性能会急剧下降。
2. Swiss Table 的核心思想
Swiss Table 是一种开放地址法的哈希表,但它采用了多种优化策略来解决传统哈希表的问题,主要包括:
SIMD (Single Instruction, Multiple Data) 指令: 利用现代 CPU 的 SIMD 指令,一次性比较多个桶的状态,加速查找过程。
元数据 (Metadata) 存储: 将每个桶的元数据(例如,是否为空、是否被删除、哈希值的一部分)存储在一个独立的数组中,与实际的键值对分开。这使得我们可以快速地扫描元数据,而无需访问可能不在缓存中的键值对数据。
组 (Group) 的概念: 将哈希表分成多个固定大小的组(例如,16 个桶为一组),对每个组的元数据进行批量操作。
特殊的哈希函数: 使用一种称为 H2的哈希函数,它的计算成本较低,且具有良好的分布特性。以及一个H1来做最后取模运算。
3. Swiss Table 的数据结构
Swiss Table 的核心数据结构主要包括:
keys 数组: 存储所有的键。
values 数组: 存储所有的值。
ctrl 数组 (控制字节数组): 存储每个桶的元数据。每个字节(8 位)代表一个桶的状态,其中:
最高位(第 7 位):表示桶是否为空(EMPTY)。值为 1 表示空。
低 7 位(第 0-6 位):存储键的哈希值的低 7 位(H2 哈希值的一部分)。这部分用于快速过滤不匹配的键。
特殊的几个值:
EMPTY (0b1111_1111, all bits set): 表示桶为空。
DELETED (0b1000_0000, only the high bit set): 表示桶已被删除(墓碑)。
MATCH (any other value): 表示桶中可能包含匹配的键(需要进一步比较完整的键)。
**growth_left:**记录了距离下一次扩容还差多少个空闲的桶
**items:**已经存了多少个元素
buckets: 目前桶的数量
mask: 等于buckets -1, 使用这个mask来快速将hash值对桶数量取模
一个图示:
+-----------------+-----------------+-----------------+ ... +-----------------+
| ctrl[0] (元数据) | ctrl[1] (元数据) | ctrl[2] (元数据) | ... | ctrl[N-1] (元数据) | <-- ctrl 数组 (控制字节)
+-----------------+-----------------+-----------------+ ... +-----------------+
+-----------------+-----------------+-----------------+ ... +-----------------+
| keys[0] (键) | keys[1] (键) | keys[2] (键) | ... | keys[N-1] (键) | <-- keys 数组
+-----------------+-----------------+-----------------+ ... +-----------------+
+-----------------+-----------------+-----------------+ ... +-----------------+
| values[0] (值) | values[1] (值) | values[2] (值) | ... | values[N-1] (值) | <-- values 数组
+-----------------+-----------------+-----------------+ ... +-----------------+
每个 ctrl 字节的结构:
+---+---+---+---+---+---+---+---+
| E | H2[6] | H2[5] | H2[4] | H2[3] | H2[2] | H2[1] | H2[0] | <-- E: EMPTY 位, H2: 哈希值的低 7 位
+---+---+---+---+---+---+---+---+
content_copy
download
Use code with caution.
4. Swiss Table 的操作
插入 (Insert):
计算键的 H1 和 H2 哈希值。
根据 H1 哈希值找到起始桶的索引。
从起始桶开始,按组(Group)扫描 ctrl 数组。
在每个组内,使用 SIMD 指令同时比较多个桶的 ctrl 字节和 H2 哈希值的低 7 位。
如果找到一个 EMPTY 桶,将键值对插入到对应的 keys 和 values 数组中,并将 ctrl 字节设置为 H2 哈希值的低 7 位。
如果找到一个 DELETED 桶,可以先记录下来(作为备选插入位置),继续查找。
如果遇到一个 MATCH 桶,需要比较完整的键是否相等。如果相等,则更新对应的值。
如果整个组都没有找到空桶或匹配的键,则继续探测下一个组。
如果探测了一定数量的组后仍然没有找到插入位置,则触发扩容操作。
如果找到了DELETED桶,则选择第一个遇到的DELETED桶,将键值对插入到对应的 keys 和 values 数组中,并将 ctrl 字节设置为 H2 哈希值的低 7 位。
更新growth_left与items
查找 (Lookup):
计算键的 H1 和 H2 哈希值。
根据 H1 哈希值找到起始桶的索引。
从起始桶开始,按组扫描 ctrl 数组。
在每个组内,使用 SIMD 指令同时比较多个桶的 ctrl 字节和 H2 哈希值的低 7 位。
如果找到一个 MATCH 桶,需要比较完整的键是否相等。如果相等,则返回对应的值。
如果遇到一个 EMPTY 桶,表示键不存在,查找结束。
如果整个组都没有找到匹配的键,则继续探测下一个组。
如果探测到哈希表末尾,则查找结束,键不存在。
删除 (Delete):
按照查找的步骤找到要删除的键。
如果找到,将对应 ctrl 字节设置为 DELETED(墓碑标记)。
将items减一。
注意:不能简单地将 ctrl 字节设置为 EMPTY,因为这可能会中断后续的探测序列。
扩容 (Resize):
分配一个更大的 ctrl、keys 和 values 数组(通常是原来的两倍)。
遍历旧的 ctrl 数组,将所有非 EMPTY 和非 DELETED 的桶重新插入到新的哈希表中。
释放旧的数组。
5. SIMD 指令的应用
Swiss Table 的一个关键优化是使用 SIMD 指令来加速查找过程。SIMD 指令允许 CPU 在一个指令周期内对多个数据执行相同的操作。
#系统资讯 谨防恶作剧者!使用这个 KEY 激活 Windows 10/11 系统会导致循环崩溃只能重装。
在 X/Twitter 上有网友发现某个神奇的 KEY,安装这个 KEY 后系统会自动重启接着就是循环崩溃,目前没有任何办法能够修复系统。至于这个 KEY 为什么会导致系统循环崩溃目前也不清楚,这可能会被恶作剧者利用,各位在网上寻找密钥时应谨慎避免上当。
查看全文:https://ourl.co/105995
在 X/Twitter 上有网友发现某个神奇的 KEY,安装这个 KEY 后系统会自动重启接着就是循环崩溃,目前没有任何办法能够修复系统。至于这个 KEY 为什么会导致系统循环崩溃目前也不清楚,这可能会被恶作剧者利用,各位在网上寻找密钥时应谨慎避免上当。
查看全文:https://ourl.co/105995
BFXT4-MY2GY-MBCRK-PBN7B-HT963
在不安全的浏览器环境下,浏览器的密码学 API 可能会受限。例如:在使用现代 chromium 内核时,当前域为非 https 的 ip 地址时
crypto.subtle.digest 会不可用(TypeError: Cannot read properties of undefined (reading 'digest')),当前域为 localhost 或者 https 环境时 crypto.subtle.digest 是可用的。