关于Go1.14,你一定想知道的性能提升与新特性

栏目: IT技术 · 发布时间: 4年前

内容简介: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 的关系。

关于Go1.14,你一定想知道的性能提升与新特性

每个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。

关于Go1.14,你一定想知道的性能提升与新特性

在小顶堆中,父节点比其他四个节点都小,子节点之前没有大小关系。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

鼠标宣言

鼠标宣言

约翰·里德尔 / 倪萍、梅清豪 / 上海人民 / 2005-08-01 / 25.00

本书针对信息时代营销者不知该如何满足消费者的营销困境,提出了崭新的解决方案——以新技术为基础的群体筛选和推荐系统。随着信息管理软件和internet的高速发展,群体筛选技术下的推荐系统通过大量有关消费者偏好和购物记录的信息,以及对产品特征的准确把握,能够为消费者进行精确的推荐,提高了消费者的购物效率和准确度以及营销者的营销效率和竞争力。本书通过通俗而到位的讲解,向读者全面介绍了有关群体筛选技术的理......一起来看看 《鼠标宣言》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码