Go语言回顾:从Go 1.0到Go 1.13

Go 1.13版本在2019.9.3正式发布!国外的Gopher Vincent Blanchon发表了一篇文章《Go: Retrospective》(科学上网阅读) , 对Go从1.0版本到1.13版本做了简要的回顾 , 这里是那篇文章的译文 。

Go语言回顾:从Go 1.0到Go 1.13

文章插图
 
对于每一位Go开发者来说 , Go语言的演化历程是必须要知道的事情 。了解这些横跨年份发布的大版本的主要变化将有助于Gopher理解Go语言的发展理念以及该语言每个版本的优势与不足 。更多关于特定版本的变更细节 , 可以参考每个版本对应的Changelog 。
Go 1.0 – 2012.3月伴随着Go语言的第一个版本 , Go的缔造者还发布了一份兼容性文档 。该文档保证未来的Go版本将保持向后兼容性(backward-compatible) , 即始终兼容已有的代码 , 保证已有代码在Go新版本下编译和运行的正确性 。
Go 1.0版本还包含了go tool pprof命令 , 这是一个google pprof C++ profiler的变体 。Go 1.0还提供了go vet命令(之前的go tool vet) , 用于报告Go package中可能的错误 。
Go 1.1 – 2013.5月该版本主要专注于语言改善和性能提升(编译器、垃圾回收、map、goroutine调度) 。这里是一个改善后的效果示意图:
Go语言回顾:从Go 1.0到Go 1.13

文章插图
 
图来自https://dave.cheney.net/2013/05/21/go-11-performance-improvements
这个版本同时还嵌入了一个竞态探测器(race detector) , 这个工具对于Go这种原生并发的语言是十分必要的 。在《Race Detector with ThreadSanitizer”》一文中 , 你可以找到有关race detector的更多详细信息 。
在这个版本中的一个重点变动是Goroutine调度器被重写了 , 重写后的调度器性能大幅提升 。
重写后的Go调度器的设计如下图:
Go语言回顾:从Go 1.0到Go 1.13

文章插图
 
图来自 https://rakyll.org/scheduler/
M对应的是操作系统的线程 。P表示一个处理器(P的数量不能超过GOMAXPROCS) , 每个P拥有一个本地goroutine队列 。在1.1版本之前 , P这个抽象并不存在 。所有goroutine的调度通过全局互斥锁进行全局级别的管理 。这次改进实现了”work-stealing”算法 , 允许某个P从其他P的队列中”偷goroutine”:
Go语言回顾:从Go 1.0到Go 1.13

文章插图
 
图来自 https://rakyll.org/scheduler/
更多关于Go调度器调度原理以及”work-stealing”算法的信息 , 可以查看Jaana B. Dogan的文章《Go’s work-stealing scheduler》 。
Go 1.2 – 2013.12在该版本中 , Go test命令开始支持代码测试覆盖率统计了 , 并且通过go提供的新子命令: go tool cover可以查看代码测试覆盖率统计信息:
Go语言回顾:从Go 1.0到Go 1.13

文章插图
 
图来自 https://blog.golang.org/cover
它还能提供代码覆盖信息:
Go语言回顾:从Go 1.0到Go 1.13

文章插图
 
图来自 https://blog.golang.org/cover
Go 1.3 – 2014.6该版本包含了栈管理的一个重要改进 。在该版本中 , 栈内存分配采用连续段(contiguous segment)的分配模式以提升内存分配效率 。这将为下一个版本将栈size降到2KB奠定基础 。之前的分割栈分配方式(segment stack)存在频繁分配/释放栈段导致栈内存分配性能不稳定(较低)的问题 , 引入新机制后 , 分配稳定性和性能都有较大改善 。
这里是一个json包的例子 , 图中显示json包对栈size的敏感度:
Go语言回顾:从Go 1.0到Go 1.13

文章插图
 
【Go语言回顾:从Go 1.0到Go 1.13】图来自 contiguous stack
使用连续段的栈内存分配管理模式解决了一些程序性能低下的问题 。下面是html/template包的性能对stack size的敏感度图:
Go语言回顾:从Go 1.0到Go 1.13

文章插图
 
更多信息可参见[《How Does the Goroutine Stack Size Evolve?”》(https://medium.com/@blanchon.vincent/go-how-does-the-goroutine-stack-size-evolve-447fc02085e5)] 。
这个版本还发布了sync.Pool 。这个组件允许我们后面重用结构体 , 减少内存分配的次数 。它也将成为Go生态圈中许多性能提升的源头 , 比如:标准库中的encoding/json、net/http或是Go社区中的zap等 。


推荐阅读