【问题标题】:Check if key exists in map storing large values检查存储大值的映射中是否存在键
【发布时间】:2022-01-02 16:53:12
【问题描述】:

要知道一个键 k 存在于映射 M1[k]v 中,在 Go 中非常简单。

if v, ok := M1[k]; ok {
    // key exist
}

'v':非指针类型的值。

如果v 很大,使用上述方法仅检查特定键是否存在效率不高,因为它会将值v 加载到内存中(即使我在内存中使用空白标识符_ v 的位置,根据我的理解,如果我的理解有误,请在这里纠正我。

是否有一种有效的方法可以检查 Map 中是否存在键(无需读取/或在内存中分配值)?

我正在考虑创建一个新地图M2[k]bool 来存储信息,并在每次我在M1 中插入一些内容时在M2 中输入一个条目。

【问题讨论】:

  • 使用if _, ok := M1[k]; ok { }。如果您使用空白标识符,则不会“加载”该值。
  • 如果存在特定键,使用上述方法效率不高,因为它会将值 v 加载到内存中 [...] 是什么让你相信?跨度>
  • @icza 感谢您的回复。去吧
  • mapaccess2_fat 和朋友。运行时返回一个指向映射值的指针。指向的值仅在应用程序使用时才会被复制。
  • @PenélopeStevens 感谢上述链接。

标签: dictionary go go-map


【解决方案1】:

使用if _, ok := M1[k]; ok { }。如果使用空白标识符,则不会“加载”该值。

让我们编写基准来测试它:

var m = map[int][1_000_000]int64{
    1: {},
}

func BenchmarkNonBlank(b *testing.B) {
    for i := 0; i < b.N; i++ {
        if v, ok := m[1]; ok {
            if false {
                _ = v
            }
        }
    }
}

func BenchmarkBlank(b *testing.B) {
    for i := 0; i < b.N; i++ {
        if _, ok := m[1]; ok {
            if false {
                _ = ok
            }
        }
    }
}

运行go test -bench .,输出为:

BenchmarkNonBlank-8         1497            763278 ns/op
BenchmarkBlank-8        97802791                12.09 ns/op

如您所见,使用空白标识符,该操作大约需要 10 ns。当我们将值分配给非空白标识符时,当值类型的大小约为 8 MB 时,它几乎是 1 毫秒(几乎慢了十万倍)。

【讨论】:

  • sync.Map.Load() 的行为是否也相同?我浏览了官方文档pkg.go.dev/sync#Map.Load,如果我们使用空白标识符,它没有说明分配。
  • @PrakashPandey 这是一个完全不同的故事,因为sync.Map 不是通用的(如内置地图类型),因此存储的值必须包装在interface{} 中。 Map.Load() 也是一个函数调用,无论你是否使用它们,它都必须返回值。我的答案显示了如何对其进行基准测试,修改我的基准以自己进行测试。
  • 我想我得到了答案。因为map 是内置的,sync. Map 只是一个结构+Map.Load() 是一个普通的func 调用,所以它总是会分配内存中的值。并感谢分享上述基准代码。
  • @PrakashPandey 调用sync.Map.Load() 复制了interface{}interface{} 值是两个机器字。
  • @PenélopeStevens 谢谢。如果有人想了解更多关于 interface{} value two machine words 的信息,可以在 StackOverflow 的另一篇文章中给出一个很好的解释:stackoverflow.com/a/23148998/4408364
猜你喜欢
  • 2019-08-20
  • 1970-01-01
  • 2018-03-31
  • 1970-01-01
  • 1970-01-01
  • 2019-08-22
  • 2018-11-28
  • 2021-06-11
相关资源
最近更新 更多