Go 中 Set 的最佳实现方案
发布日期:2022-02-01 16:53:57 浏览次数:40 分类:技术文章

本文共 3943 字,大约阅读时间需要 13 分钟。

Go 的设计是一种简单哲学,它摒弃了其他语言一些臃肿的功能和模块,以降低程序员的学习门槛,减少使用中的心智负担。

本文,我们来探讨 Go 中缺失的数据结构:Set,以及它的最佳实现方案。

Set 语义与实现方案

Set 集合是其他语言中常见的数据结构。特性:集合中的对象不按特定的方式排序,并且没有重复对象。

学习 Go ,要记住:Go 没有包含的东西,不代表 Go 真的没有。根据 Set 特性,我们可以很轻松地想到使用 map 的实现方案(因为 map 的 key 是不重复的):把对象当做 key 存入 map。

使用 map 来实现 Set,意味着我们只关心 key 的存在,其 value 值并不重要。有其他语言编程经验的人也许会选择 bool 来作为 value,因为它是其它语言中内存消耗最少的类型(1个字节)。但是在 Go 中,还有另一种选择:struct{}

fmt.Println(unsafe.Sizeof(struct {}{})) // output: 0

压测对比

为了探究哪种数据结构是作为 value 的最佳选择。我们选择了以下常用的类型作为 value 进行测试:boolintinterface{}struct{}

package mainimport ( "testing")const num = int(1 << 24)// 测试 bool 类型func Benchmark_SetWithBoolValueWrite(b *testing.B) { set := make(map[int]bool) for i := 0; i < num; i++ {  set[i] = true }}// 测试 interface{} 类型func Benchmark_SetWithInterfaceValueWrite(b *testing.B) { set := make(map[int]interface{}) for i := 0; i < num; i++ {  set[i] = struct{}{} }}// 测试 int 类型func Benchmark_SetWithIntValueWrite(b *testing.B) { set := make(map[int]int) for i := 0; i < num; i++ {  set[i] = 0 }}// 测试 struct{} 类型func Benchmark_SetWithStructValueWrite(b *testing.B) { set := make(map[int]struct{}) for i := 0; i < num; i++ {  set[i] = struct{}{} }}

我们运行以下命令,进行测试

$ go test -v -bench=. -count=3 -benchmem | tee result.txtgoos: darwingoarch: amd64pkg: workspace/example/demoForSetcpu: Intel(R) Core(TM) i5-8279U CPU @ 2.40GHzBenchmark_SetWithBoolValueWriteBenchmark_SetWithBoolValueWrite-8                1 3549312568 ns/op 883610264 B/op   614311 allocs/opBenchmark_SetWithBoolValueWrite-8                1 3288521519 ns/op 883599440 B/op   614206 allocs/opBenchmark_SetWithBoolValueWrite-8                1 3264097496 ns/op 883578624 B/op   614003 allocs/opBenchmark_SetWithInterfaceValueWriteBenchmark_SetWithInterfaceValueWrite-8           1 4397757645 ns/op 1981619632 B/op   614062 allocs/opBenchmark_SetWithInterfaceValueWrite-8           1 4088301215 ns/op 1981553392 B/op   613743 allocs/opBenchmark_SetWithInterfaceValueWrite-8           1 3990698218 ns/op 1981560880 B/op   613773 allocs/opBenchmark_SetWithIntValueWriteBenchmark_SetWithIntValueWrite-8                 1 3472910194 ns/op 1412326480 B/op   615131 allocs/opBenchmark_SetWithIntValueWrite-8                 1 3519755137 ns/op 1412187928 B/op   614294 allocs/opBenchmark_SetWithIntValueWrite-8                 1 3459182691 ns/op 1412057672 B/op   613390 allocs/opBenchmark_SetWithStructValueWriteBenchmark_SetWithStructValueWrite-8              1 3126746088 ns/op 802452368 B/op   614127 allocs/opBenchmark_SetWithStructValueWrite-8              1 3161650835 ns/op 802431240 B/op   613632 allocs/opBenchmark_SetWithStructValueWrite-8              1 3160410871 ns/op 802440552 B/op   613748 allocs/opPASSok   workspace/example/demoForSet 42.660s

此时的结果看起来不太直观,这里推荐一个 benchmark 统计工具:Benchstat。通过以下命令进行安装

$ go get -u golang.org/x/perf/cmd/benchstat

使用 benchstat 分析刚才得到的 benchmark 结果文件

$ benchstat result.txtname                           time/op_SetWithBoolValueWrite-8        3.37s ± 5%_SetWithInterfaceValueWrite-8   4.16s ± 6%_SetWithIntValueWrite-8         3.48s ± 1%_SetWithStructValueWrite-8      3.15s ± 1%name                           alloc/op_SetWithBoolValueWrite-8        884MB ± 0%_SetWithInterfaceValueWrite-8  1.98GB ± 0%_SetWithIntValueWrite-8        1.41GB ± 0%_SetWithStructValueWrite-8      802MB ± 0%name                           allocs/op_SetWithBoolValueWrite-8         614k ± 0%_SetWithInterfaceValueWrite-8    614k ± 0%_SetWithIntValueWrite-8          614k ± 0%_SetWithStructValueWrite-8       614k ± 0%

从内存开销而言,struct{} 是最小的,反映在执行时间上也是最少的。由于 bool 类型仅占一个字节,它相较于空结构而言,相差的并不多。但是,如果使用 interface{} 类型,那差距就很明显了。

所以,毫无疑问,在 Set 的实现中, map 值类型应该选 struct{}。

总结

本文虽然讨论的是 Set 的实现方案,但本质是涉及空结构体 struct{}{} 的 零内存特性。

空结构体除了是实现 Set 的 value 值最佳方案,它还可以应用于以下方面:

  • 通知信号的 channel:当 channel 只用于通知 goroutine 的执行事件,此时 channel 就不需要发送任何实质性的数据,选择使用 chan struct{}

  • 没有状态数据的结构体:当对象只拥有方法,而不包含任何的属性字段时,选择使用空结构体定义该对象。

往期推荐

机器铃砍菜刀

欢迎添加小菜刀微信

加入Golang分享群学习交流!

感谢你的点赞在看哦~

转载地址:https://blog.csdn.net/slphahaha/article/details/120279159 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Go sync.Pool 浅析
下一篇:一个易中招的 Go 内存泄露案例

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2024年04月04日 11时33分50秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

Exchange 2010 过期日历项目的删除! 2019-04-26
使用Admodify 删除Exchange中多余的邮件地址! 2019-04-26
如何将Exchange 的数据变成可以编辑及计算的数据? 2019-04-26
如果运行不了VBS脚本? 2019-04-26
制作企业版中的自解压安装包? 2019-04-26
使用IIFP同步两个不同域的GAL进行同步! 2019-04-26
基于NLB的 RMS部署! 2019-04-26
利用Powershell 批量创建文件夹! 2019-04-26
Lync 客户端无法及时更新! 2019-04-26
用IOT的思维来管理我们的查看我们重要业务的服务器健康状态。-概述篇 2019-04-26
用IOT的思维来管理我们的查看我们重要业务的服务器健康状态-Azure配置篇! 2019-04-26
用IOT的思维来管理我们的查看我们重要业务的服务器健康状态-Powershell模块配置篇! 2019-04-26
用IOT的思维来管理我们的查看我们重要业务的服务器健康状态-将IOT设备注册到设备中心! 2019-04-26
用IOT的思维来管理我们的查看我们重要业务的服务器健康状态-Powershell脚本编写-发送脚本解析! 2019-04-26
用IOT的思维来管理我们的查看我们重要业务的服务器健康状态-接受消息脚本编写! 2019-04-26
用IOT的思维来管理我们的查看我们重要业务的服务器健康状态-脚本功能性测试! 2019-04-26
流批一体神器 Flink 已成气候!!! Spark 这下彻底没戏了? 2019-04-26
手绘关联规则挖掘算法 2019-04-26
砖厂新鲜出炉的 Spark 3.1.1香在哪? 2019-04-26
京东金融大数据平台架构(附82页PPT) 2019-04-26