Map2
一、SwissTable
为了进一步的优化性能、缓存命中率,Go从桶换成了SwissTable
SwissTable就是一个map有多个group,每个group有16个槽位,每个槽位有一个 metadata与之对应

给一个key,进行hash之后。得到一个64比特的数。
- 哈希值的高57比特被称为H1,用于定位 哪个 Group
- 低7比特被称为H2。放在metadata中存储,用于快速过滤。
- 左侧再加一位,用于特殊含义:如果是1000 0000,说明是空,如果是1111 1110,说明是deleted / tombstone,如果是0xxx xxxx说明满了


SIMD
Swiss Table 最大优势在 SIMD(Single Instruction, Multiple Data),一条指令,同时处理多个数据。
在过滤metadata中,我们不是for循环逐个比较,而是使用SIMD方法一次性比较16个。
- CPU提供了向量寄存器,可以在一条指令中比较多个字节
- 查询时,先将目标 Key 的 H2 广播填充至整个向量寄存器
- 再将 Group 的 16 个 Metadata 一次性加载进另一个寄存器。
- 利用 SIMD 指令让这两组数据一触碰,就能瞬间筛选出所有匹配的 Slot

二、Go 1.24实现
Go Map 1.24做了点适配:
- 核心架构 :为了避免大 Map 在扩容时因"全量搬迁"导致的瞬间卡顿,Go 将 Map 拆分成了多个独立的 Table
- 路由中心 :Map 持有一个 Directory(目录),它本质上是一个指向各个 Table 的指针数组。
- 优化: 当元素非常小时,Directory会指向一个单独的组Group,并将表长度置为0。

1. GlobalDepth和LocalDepth
Directory.GlobalDepth 决定了目录的大小,即 2^GlobalDepth 个槽位。
- 查找时,取哈希值的高几位作为索引去 Directory 里"查表"。
- 重点:不同的 Directory 条目可能指向同一个物理 Table。当某个 Table 满了,它会独立分裂,而其他 Table 保持不动。

Table.LocalDepth 决定了具体的group在目录占了几行,与扩容有关,后面再讲
2. 查找流程
- 根据哈希值的高位构造探测序列
- 循环遍历
- SIMD快速匹配候选人
- 逐个比较候选人的键
- 如果找到,返回值
- 如果未找到,看是否有空元素
- 有则说明探测终止,直接返回未找到
- 无空元素,那就根据探测序列跳到下一个Group,继续找

3. 修改、增加流程
- 根据哈希值的高位构造探测序列
- 循环遍历
- SIMD快速匹配候选人
- 逐个比较候选人的键
- 如果找到,直接更新,退出
- 如果未找到,看是否有墓碑
- 有则记录第一个墓碑在哪,继续找
- 未找到,并且到达终点,优先填坑墓碑,不行就填坑空位
- 没位置则进行扩容

4. 删除流程
- 根据哈希值的高位构造探测序列
- 循环遍历
- SIMD快速匹配候选人
- 逐个比较候选人的键
- 如果找到, 是指针就清零, 不是指针就清内存
- 未找到,则继续下一个Group,直到到达终点
- 清空后,看组里有没有空位
- 有,说明是探测序列终点,可以置metadata为空
- 没有,说明组内满员,不是终点,置为墓碑(ctrlDeleted)

5. 扩容流程
计算当前容量*2是否小于规定的单表最大限制
- 小于,则翻倍扩容,分配一个两倍大的新表,把旧数据重新哈希搬过去,替换旧表
- 否则分裂扩容,创建两个最大容量的新表,数据分流搬迁,看是否需要扩容目录


Table.LocalDepth 决定了具体的group在目录占了几行 计算公式:指向同一个 Group 的目录项数量 = 2^(GlobalDepth - LocalDepth)

6. 边遍历边修改
由于 Go 慷慨的迭代语义,迭代是 Map 中最复杂的部分。规范中的语义摘要:
- 在迭代期间添加和/或删除条目绝不能导致迭代多次返回同一个条目。
- 在迭代期间添加的条目可能会被迭代返回。
- 在迭代期间修改的条目必须返回其最新值。
- 在迭代期间删除的条目绝不能被迭代返回。
- 迭代顺序是未指定的。在实现中,它是被显式随机化的。
直接遍历旧表,然后读值的时候读新表