面试官:为什么 Go 的负载因子是 6.5?

339次阅读  |  发布于3年以前

最近我有一个朋友,在网上看到一个有趣的段子,引发了我一些兴趣。

如下图:

听说是在最后的闲聊、吹水、聊人生、乱扯环节了,不是在技术环节了,所以大家也不用太在意什么技术评估法则(别杠)。

煎鱼作为一名技术号主,看到这里的 6.5,就想给大家挖一挖,这到底是何物,和大家一同学习和增长知识!

6.5 是什么

实际上在 Go 语言中,就存在 6.5 这一概念,与 map 存在直接关系,因此我们需要先了解 map 的基本数据结构,再介绍 6.5 的背景和由来。

开始学习 6.5 的前世今生吧!

了解 map 底层

我以前在写《深入理解 Go map:初始化和访问元素》时有介绍过 map 的基础数据结构。

基本结构如下图:

map 基本数据结构

其中重要的一个基本单位是 hmap:

type hmap struct {
 count     int
 flags     uint8
 B         uint8
 noverflow uint16
 hash0     uint32
 buckets    unsafe.Pointer
 oldbuckets unsafe.Pointer
 nevacuate  uintptr
 extra *mapextra
}

type mapextra struct {
 overflow    *[]*bmap
 oldoverflow *[]*bmap
 nextOverflow *bmap
}

我们关注到 hmap 的 B 字段,其值就是 6.5,他就是我们在苦苦寻找的 6.5,但他又是什么呢?

什么是负载因子

B 值,这里就涉及到一个概念:负载因子(load factor),用于衡量当前哈希表中空间占用率的核心指标,也就是每个 bucket 桶存储的平均元素个数。

另外负载因子与扩容、迁移等重新散列(rehash)行为有直接关系:

负载因子是哈希表中的一个重要指标,在各种版本的哈希表实现中都有类似的东西,主要目的是为了平衡 buckets 的存储空间大小和查找元素时的性能高低

在接触各种哈希表时都可以关注一下,做不同的对比,看看各家的考量。

为什么是 6.5

了解是什么后,我们进一步深挖。

为什么 Go 语言中哈希表的负载因子是 6.5,为什么不是 8 ,也不是 1。这里面有可靠的数据支撑吗?

测试报告

实际上这是 Go 官方的经过认真的测试得出的数字,一起来看看官方的这份测试报告。

报告中共包含 4 个关键指标,如下:

loadFactor %overflow bytes/entry hitprobe missprobe
4.00 2.13 20.77 3.00 4.00
4.50 4.05 17.30 3.25 4.50
5.00 6.85 14.77 3.50 5.00
5.50 10.55 12.94 3.75 5.50
6.00 15.27 11.67 4.00 6.00
6.50 20.90 10.79 4.25 6.50
7.00 27.14 10.15 4.50 7.00
7.50 34.03 9.73 4.75 7.50
8.00 41.10 9.40 5.00 8.00

选择数值

结合测试报告一看,好家伙,不测不知道,一测吓一跳,有依据了。

Go 官方发现:负载因子太大了,会有很多溢出的桶。太小了,就会浪费很多空间(too large and we have lots of overflow buckets, too small and we waste a lot of space)。

来自 Go 官方源码说明

根据这份测试结果和讨论,Go 官方把 Go 中的 map 的负载因子硬编码为 6.5,这就是 6.5 的选择缘由。

这意味着在 Go 语言中,当 B(bucket)平均每个存储的元素大于或等于 6.5 时,就会触发扩容行为,这是作为我们用户对这个数值最近的接触。

总结

在今天这篇文章中,我们先快速了解了 Go 语言中 map 的基本数据结构和设计,这和我们要解释的问题紧密相关。

紧接着针对开头所提出的 6.5,进行了介绍和说明,这其实是 map 中的负载因子。其数值的确定来源于 Go 官方的测试。

为什么是 6.5,你懂了吗?

参考

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8