使用 Swiss Table 怎样实现更快的 Go map

打印 上一主题 下一主题

主题 972|帖子 972|积分 2916

文章精选推荐

1 JetBrains Ai assistant 编程工具让你的工作服从翻倍
2 Extra Icons:JetBrains IDE的图标增强神器
3 IDEA插件推荐-SequenceDiagram,主动生成时序图
4 BashSupport Pro 这个ides插件主要是用来干嘛的 ?
5 IDEA必装的插件:Spring Boot Helper的使用与功能特点
6 Ai assistant ,又是一个写代码神器
7 Cursor 设备ID修改器,你的Cursor又可以继续试用了

文章正文

在 Go 语言中,map 是一种非常常用的数据结构,用于存储键值对。然而,在高并发和高性能的场景下,标准库中的 map 实现大概无法满足需求。Swiss Table 是一种高效的哈希表实现,最初由 Google 在 C++ 中引入,厥后也被其他语言(如 Rust)采用。本文将探讨怎样使用 Swiss Table 的思想来实现一个更快的 Go map。
1. Swiss Table 简介

Swiss Table 是一种基于开放寻址法的哈希表实现,具有以下特点:

  • 缓存友好:Swiss Table 通过将元数据(如哈希值的部分位)存储在连续的内存块中,进步了缓存掷中率。
  • SIMD 优化:Swiss Table 使用 SIMD(单指令多数据流)指令来加速查找操作。
  • 低内存开销:Swiss Table 通过紧凑的元数据存储,减少了内存开销。
2. Go 中的 Swiss Table 实现

虽然 Go 语言自己没有直接提供 Swiss Table 的实现,但我们可以借鉴其思想来实现一个高效的哈希表。以下是一个简化版的 Swiss Table 实现。
2.1 数据结构

首先,我们定义哈希表的数据结构:
  1. package swisstable
  2. import (
  3.         "unsafe"
  4. )
  5. const (
  6.         groupSize    = 16 // 每个组的大小
  7.         empty        = 0  // 空槽位标记
  8.         deleted      = 1  // 删除槽位标记
  9.         metadataSize = groupSize / 8 // 每个组的元数据大小
  10. )
  11. type entry struct {
  12.         key   string
  13.         value interface{}
  14. }
  15. type SwissTable struct {
  16.         metadata []byte // 元数据数组
  17.         entries  []entry // 存储键值对的数组
  18.         size     int     // 当前存储的键值对数量
  19.         capacity int     // 哈希表的总容量
  20. }
复制代码
2.2 哈希函数

Swiss Table 使用哈希函数来确定键的位置。我们可以使用 Go 内置的哈希函数:
  1. func hash(key string) uint64 {
  2.         h := uint64(5381)
  3.         for i := 0; i < len(key); i++ {
  4.                 h = (h << 5) + h + uint64(key[i])
  5.         }
  6.         return h
  7. }
复制代码
2.3 查找操作

查找操作是 Swiss Table 的核心。我们通过哈希值的一部分来确定键所在的组,然后在该组中查找键:
  1. func (st *SwissTable) find(key string) (int, bool) {
  2.         h := hash(key)
  3.         groupIndex := int(h % uint64(st.capacity/groupSize))
  4.         start := groupIndex * groupSize
  5.         for i := 0; i < groupSize; i++ {
  6.                 index := start + i
  7.                 if index >= st.capacity {
  8.                         index -= st.capacity
  9.                 }
  10.                 metadata := st.metadata[index/metadataSize]
  11.                 bit := byte(1 << (index % metadataSize))
  12.                 if metadata&bit == 0 {
  13.                         return -1, false // 未找到
  14.                 }
  15.                 if st.entries[index].key == key {
  16.                         return index, true // 找到
  17.                 }
  18.         }
  19.         return -1, false // 未找到
  20. }
复制代码
2.4 插入操作

插入操作首先查找键是否存在,如果存在则更新值,否则插入新键值对:
  1. func (st *SwissTable) Insert(key string, value interface{}) {
  2.         index, exists := st.find(key)
  3.         if exists {
  4.                 st.entries[index].value = value
  5.                 return
  6.         }
  7.         if st.size >= st.capacity {
  8.                 st.resize()
  9.         }
  10.         h := hash(key)
  11.         groupIndex := int(h % uint64(st.capacity/groupSize))
  12.         start := groupIndex * groupSize
  13.         for i := 0; i < groupSize; i++ {
  14.                 index := start + i
  15.                 if index >= st.capacity {
  16.                         index -= st.capacity
  17.                 }
  18.                 metadata := st.metadata[index/metadataSize]
  19.                 bit := byte(1 << (index % metadataSize))
  20.                 if metadata&bit == 0 {
  21.                         st.entries[index] = entry{key, value}
  22.                         st.metadata[index/metadataSize] |= bit
  23.                         st.size++
  24.                         return
  25.                 }
  26.         }
  27.         st.resize()
  28.         st.Insert(key, value)
  29. }
复制代码
2.5 删除操作

删除操作标记槽位为删除状态,但不立即开释内存:
  1. func (st *SwissTable) Delete(key string) {
  2.         index, exists := st.find(key)
  3.         if !exists {
  4.                 return
  5.         }
  6.         st.metadata[index/metadataSize] &^= byte(1 << (index % metadataSize))
  7.         st.entries[index] = entry{"", nil}
  8.         st.size--
  9. }
复制代码
2.6 扩容操作

当哈希表的负载因子过高时,我们需要扩容:
  1. func (st *SwissTable) resize() {
  2.         newCapacity := st.capacity * 2
  3.         newMetadata := make([]byte, newCapacity/metadataSize)
  4.         newEntries := make([]entry, newCapacity)
  5.         oldEntries := st.entries
  6.         st.metadata = newMetadata
  7.         st.entries = newEntries
  8.         st.capacity = newCapacity
  9.         st.size = 0
  10.         for _, entry := range oldEntries {
  11.                 if entry.key != "" {
  12.                         st.Insert(entry.key, entry.value)
  13.                 }
  14.         }
  15. }
复制代码
3. 性能对比

通过上述实现,我们可以对比标准库 map 和 Swiss Table 的性能。以下是一个简单的性能测试:
  1. package main
  2. import (
  3.         "fmt"
  4.         "time"
  5. )
  6. func main() {
  7.         // 标准库 map
  8.         start := time.Now()
  9.         m := make(map[string]interface{})
  10.         for i := 0; i < 1000000; i++ {
  11.                 m[fmt.Sprintf("key%d", i)] = i
  12.         }
  13.         fmt.Println("Standard map insert time:", time.Since(start))
  14.         // Swiss Table
  15.         start = time.Now()
  16.         st := swisstable.NewSwissTable()
  17.         for i := 0; i < 1000000; i++ {
  18.                 st.Insert(fmt.Sprintf("key%d", i), i)
  19.         }
  20.         fmt.Println("Swiss Table insert time:", time.Since(start))
  21. }
复制代码
4. 总结

通过借鉴 Swiss Table 的思想,我们可以在 Go 中实现一个高效的哈希表。虽然 Go 的标准库 map 已经非常高效,但在某些特定场景下,Swiss Table 的实现大概会带来更好的性能。未来,随着 Go 语言的发展,大概会有更多的高性能数据结构被引入标准库或第三方库中。
参考文献



  • Swiss Table: A Fast Hash Table Implementation
  • Go 语言官方文档

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

水军大提督

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表