内容简介:Go官方团队将在今年2月份发布1.14版本。相比较于之前的版本升级,Go1.14在性能提升上做了较大改动,还加入了很多新特性,我们一起来看一下Go1.14都给我们带来了哪些惊喜吧!先列举几个Go1.14在性能提升上做的改进。异常牛逼是有多牛逼呢?我们可以通过一个简单benchmark看一看。用例如下(defer_test.go):
Go官方团队将在今年2月份发布1.14版本。相比较于之前的版本升级,Go1.14在性能提升上做了较大改动,还加入了很多新特性,我们一起来看一下 Go 1.14都给我们带来了哪些惊喜吧!
1.性能提升
先列举几个Go1.14在性能提升上做的改进。
1.1 defer性能“异常”牛逼
异常牛逼是有多牛逼呢?我们可以通过一个简单benchmark看一看。用例如下(defer_test.go):
package main
import (
"testing"
)
type channel chan int
func NoDefer() {
ch1 := make(channel, 10)
close(ch1)
}
func Defer() {
ch2 := make(channel, 10)
defer close(ch2)
}
func BenchmarkNoDefer(b *testing.B) {
for i := 0; i < b.N; i++ {
NoDefer()
}
}
func BenchmarkDefer(b *testing.B) {
for i := 0; i < b.N; i++ {
Defer()
}
}
复制代码
我们分别使用Go1.13版本和Go1.14版本进行测试,关于Go多个版本的管理切换,推荐大家使用 gvm ,非常的方便。首先使用Go1.13版本,只需要命令: gvm use go1.13 ;之后运行命令: go test -bench=. -v ,结果如下:
goos: darwin goarch: amd64 pkg: github.com/GuoZhaoran/myWebSites/data/goProject/defer BenchmarkNoDefer-4 15759076 74.5 ns/op BenchmarkDefer-4 11046517 102 ns/op PASS ok github.com/GuoZhaoran/myWebSites/data/goProject/defer 3.526s 复制代码
可以看到,Go1.13版本调用defer关闭channel的性能开销还是蛮大的,op几乎差了30ns。切换到go1.14: gvm use go1.14 ;再次运行命令: go test -bench=. -v ,下面的结果一定会亮瞎了小伙伴的双眼:
goos: darwin goarch: amd64 pkg: github.com/GuoZhaoran/myWebSites/data/goProject/defer BenchmarkNoDefer BenchmarkNoDefer-4 13094874 80.3 ns/op BenchmarkDefer BenchmarkDefer-4 13227424 80.4 ns/op PASS ok github.com/GuoZhaoran/myWebSites/data/goProject/defer 2.328s 复制代码
Go1.14版本使用defer关闭channel几乎0开销!
关于这一改进,官方给出的回应是: Go1.14提高了defer的大多数用法的性能,几乎0开销!defer已经可以用于对性能要求很高的场景了。
关于defer,在Go1.13版本已经做了一些的优化,相较于Go1.12,defer大多数用法性能提升了30%。而Go1.14的此次改进更是激动人心!关于Go1.14对defer优化的原理和细节,笔者还没有收集到参考资料,相信很快就会有大神整理出来,大家可以关注一下。关于Go语言defer的设计原理、Go1.13对defer做了哪些改进,推荐给大家下面几篇文章:
1.2 goroutine支持异步抢占
Go语言调度器的性能随着版本迭代表现的越来越优异,我们来了解一下调度器使用的G-M-P模型。先是一些概念:
- G(Goroutine): goroutine,由关键字go创建
- M(Machine): 在Go中称为Machine,可以理解为工作线程
- P(Processor) : 处理器 P 是线程 M 和 Goroutine 之间的中间层(并不是CPU)
M必须持有P才能执行G中的代码,P有自己本地的一个运行队列runq,由可运行的G组成,Go语言调度器的工作原理就是处理器P的队列中选择队列头的goroutine 放到线程 M 上执行,下图展示了 线程 M、处理器 P 和 goroutine 的关系。
每个P维护的G可能是不均衡的,调度器还维护了一个全局G队列,当P执行完本地的G任务后,会尝试从全局队列中获取G任务运行( 需要加锁 ),当P本地队列和全局队列都没有可运行的任务时,会尝试偷取其他P中的G到本地队列运行( 任务窃取 )。
在Go1.1版本中,调度器还不支持抢占式调度,只能依靠 goroutine 主动让出 CPU 资源,存在非常严重的调度问题:
- 单独的 goroutine 可以一直占用线程运行,不会切换到其他的 goroutine,造成饥饿问题
- 垃圾回收需要暂停整个程序(Stop-the-world,STW),如果没有抢占可能需要等待几分钟的时间,导致整个程序无法工作
Go1.12中编译器在特定时机插入函数,通过函数调用作为入口触发抢占,实现了协作式的抢占式调度。但是这种需要函数调用主动配合的调度方式存在一些边缘情况,就比如说下面的例子:
package main
import (
"runtime"
"time"
)
func main() {
runtime.GOMAXPROCS(1)
go func() {
for {
}
}()
time.Sleep(time.Millisecond)
println("OK")
}
复制代码
其中创建一个goroutine并挂起, main goroutine 优先调用了 休眠,此时唯一的 P 会转去执行 for 循环所创建的 goroutine,进而 main goroutine 永远不会再被调度。换一句话说在Go1.14之前,上边的代码永远不会输出OK。这是因为Go1.12实现的协作式的抢占式调度是不会使一个没有主动放弃执行权、且不参与任何函数调用的goroutine被抢占。
Go1.14 通过实现了基于信号的真抢占式调度解决了上述问题,这是一个非常大的改动,Go团队对已有的逻辑进行重构并为 goroutine 增加新的状态和字段来支持抢占。这一改动使得Go语言调度器更加健壮,调度性能更加优越,但是还有一些潜在的问题没有被发现,预计将来会在 STW 和栈扫描之外加入更多的抢占点。
关于调度器和Go语言的G-M-P并发模型,都是非常深入的话题。下边推荐给读者的几篇文章,特别值得学习探索:
1.3 time.Timer定时器性能得到“巨幅”提升
我们先来看一下官方的benchmark数据吧。 数据来源
Changes in the time package benchmarks: name old time/op new time/op delta AfterFunc-12 1.57ms ± 1% 0.07ms ± 1% -95.42% (p=0.000 n=10+8) After-12 1.63ms ± 3% 0.11ms ± 1% -93.54% (p=0.000 n=9+10) Stop-12 78.3µs ± 3% 73.6µs ± 3% -6.01% (p=0.000 n=9+10) SimultaneousAfterFunc-12 138µs ± 1% 111µs ± 1% -19.57% (p=0.000 n=10+9) StartStop-12 28.7µs ± 1% 31.5µs ± 5% +9.64% (p=0.000 n=10+7) Reset-12 6.78µs ± 1% 4.24µs ± 7% -37.45% (p=0.000 n=9+10) Sleep-12 183µs ± 1% 125µs ± 1% -31.67% (p=0.000 n=10+9) Ticker-12 5.40ms ± 2% 0.03ms ± 1% -99.43% (p=0.000 n=10+10) Sub-12 114ns ± 1% 113ns ± 3% ~ (p=0.069 n=9+10) Now-12 37.2ns ± 1% 36.8ns ± 3% ~ (p=0.287 n=8+8) NowUnixNano-12 38.1ns ± 2% 37.4ns ± 3% -1.87% (p=0.020 n=10+9) Format-12 252ns ± 2% 195ns ± 3% -22.61% (p=0.000 n=9+10) FormatNow-12 234ns ± 1% 177ns ± 2% -24.34% (p=0.000 n=10+10) MarshalJSON-12 320ns ± 2% 250ns ± 0% -21.94% (p=0.000 n=8+8) MarshalText-12 320ns ± 2% 245ns ± 2% -23.30% (p=0.000 n=9+10) Parse-12 206ns ± 2% 208ns ± 4% ~ (p=0.084 n=10+10) ParseDuration-12 89.1ns ± 1% 86.6ns ± 3% -2.78% (p=0.000 n=10+10) Hour-12 4.43ns ± 2% 4.46ns ± 1% ~ (p=0.324 n=10+8) Second-12 4.47ns ± 1% 4.40ns ± 3% ~ (p=0.145 n=9+10) Year-12 14.6ns ± 1% 14.7ns ± 2% ~ (p=0.112 n=9+9) Day-12 20.1ns ± 3% 20.2ns ± 1% ~ (p=0.404 n=10+9) 复制代码
从基准测试的结果可以看出AfterFunc、After、Ticker这些time包的性能都得到了“巨副”提升。
在Go1.10之前的版本中,Go语言使用一个全局的四叉堆的小顶堆维护所有的timer。
在小顶堆中,父节点比其他四个节点都小,子节点之前没有大小关系。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Redis 命令、特性介绍与性能调优
- Redis基础、高级特性与性能调优
- Mozilla 将主攻 WebAssembly 的性能和特性
- 利用React 16.6新特性优化应用性能
- Kotlin 1.3.30 发布,改进性能和引入新特性
- Composer 2.0 发布带来的性能优化、新特性和升级指南
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Visual Thinking
Colin Ware / Morgan Kaufmann / 2008-4-18 / USD 49.95
Increasingly, designers need to present information in ways that aid their audiences thinking process. Fortunately, results from the relatively new science of human visual perception provide valuable ......一起来看看 《Visual Thinking》 这本书的介绍吧!