

还咋优化?我是说Go程序
source link: https://colobu.com/2022/10/17/a-first-look-at-arena/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

还咋优化?我是说Go程序
Go语言是一个极容易上手的语言,而且Go程序的优化套路基本上被大家莫得清清楚楚的,如果你有心,可以在互联网上搜出很多Go程序优化的技巧,有些文章可能只介绍了几个优化点,有些文章从CPU的架构到Slice预分配,再到通过pprof找性能的瓶颈点等等全面介绍Go程序的优化,所以说可见的手段基本上被大家摸得门清,最近老貘出了一道题,如下所示,可以看到大家对Go语言的优化已经摸的多深了。
const N = 1000
var a [N]int
//go:noinline
func g0(a *[N]int) {
for i := range a {
a[i] = i // line 12
//go:noinline
func g1(a *[N]int) {
_ = *a // line 18
for i := range a {
a[i] = i // line 20
Go 官方也没闲着。虽然Go语言创立之初也并没有目标要和C++语言打平性能,但是Go团队对Go语言的编译和运行时优化也一直在进行着。
最近,Go语言也正在新加两个性能优化的特性,一个是cmd/compile: profile-guided optimization, 这个提案已经被接受, 后续功能初步成型后我们再介绍。另外一个增加memory arena。
除了大家常见的通用语言的优化外,影响Go程序性能最大的问题之一就是垃圾回收,所以使用C++、Rust开发的程序员diss Go程序的原因之一。不过这也是垃圾回收编程语言无法绕开的特性,基本上无可避免的带有STW的开销,即使没有STW,垃圾回收时也会耗资源进行对象的便利和检查,所以理论上来说Go性能相比较C+/Rust语言性能总会差一些,除非你禁用垃圾回收、纯粹做CPU计算。
Debian的 benchmark's game网站测试和公布了好多语言的一些场景的性能比较,比如下面这个是Rust和Go的几个实现版本的性能比较:
可以看到在这个二叉树的场景下Go的性能比Rust的也差很多。不过性能最好的Rust实现使用arena
的内存分配:
use bumpalo::Bump;
use rayon::prelude::*;
#[derive(Debug, PartialEq, Clone, Copy)]
struct Tree<'a> {
left: Option<&'a Tree<'a>>,
right: Option<&'a Tree<'a>>,
fn item_check(tree: &Tree) -> i32 {
if let (Some(left), Some(right)) = (tree.left, tree.right) {
1 + item_check(right) + item_check(left)
} else {
fn bottom_up_tree<'r>(arena: &'r Bump, depth: i32) -> &'r Tree<'r> {
let tree = arena.alloc(Tree { left: None, right: None });
if depth > 0 {
tree.right = Some(bottom_up_tree(arena, depth - 1));
tree.left = Some(bottom_up_tree(arena, depth - 1));
arena是一个内存池的技术,一般来说arena会创建一个大的连续内存块,该内存块只需要预先分配一次,在这块内存上的创建和释放都是手工执行的。
Go语言准备新加入 arena 的功能,并在标准库提供一个新的包: arena
。当前这个提案还是holding的状态,但是相关的代码已经陆陆续续地提到master分支了,所以说配批准也基本跑不了了,应该在Go 1.20,也就是明年春季的版本中尝试使用了。(当然也有开发者对Go的这种做法不满,因为外部开发者提出这种想法基本上被驳回或者不被关注,而Go团队的人有这想法就可以立马实现,甚至提案还没批准)。
包arena
当前提供了几个方法:
- NewArena(): 创建一个Arena, 你可以创建多个Arena, 批量创建一批对象,统一手工释放。它不是线程安全的。
- Free(): 释放Arena以及它上面创建出来的所有的对象。释放的对象你不应该再使用了,否则可能会导致意想不到的错误。
- NewT any *T: 创建一个对象
- MakeSliceT any []T: 在Arena创建一个Slice。
CloneT any: 克隆一个Arena上对象,只能是指针、slice或者字符串。如果传入的对象不是在Arena分配的,直接原对象返回,否则脱离Arena创建新的对象。
当前还没有实现
MakeMap
、MakeChan
这样在Arena上创建map和channel的方法,后续可能会加上。arena的功能为一组Go对象创建一块内存,手工整体一次性的释放,可以避免垃圾回收。毕竟,我们也提到了,垃圾回收是Go程序的最大的性能杀手之一。
官方建议在批量创建大量的Go对象的时候,每次能以Mib分配内存的场景下使用更有效,甚至他们找到了一个场景: protobuf的反序列化。
因为涉及到垃圾回收、内存分配的问题,所以这个功能实现起来也并不简单,涉及到对运行时代码的改造。不考虑垃圾回收对arena的处理, arena主要的实现在在运行时的arena.go中。因为这个功能还在开发之中,或许这个文件还会有变更。
接下来,我们使用debian benchmark's game的二叉树的例子,对使用arena和不使用arena的情况下做一个比较:
package main
import (
"arena"
"flag"
"fmt"
"strconv"
"time"
// gotip run -tags "goexperiment.arenas" main.go -arena 21
// GOEXPERIMENT=arenas gotip run main.go -arena 21
var n = 0
type Node struct {
left, right *Node
value []byte
func bottomUpTree(depth int) *Node {
if depth <= 0 {
return &Node{}
return &Node{bottomUpTree(depth - 1), bottomUpTree(depth - 1), make([]byte, 128, 128)}
func bottomUpTreeWithArena(depth int, a *arena.Arena) *Node {
node := arena.New[Node](a)
node.value = arena.MakeSlice[byte](a, 128, 128)
if depth <= 0 {
return node
node.left = bottomUpTreeWithArena(depth-1, a)
node.right = bottomUpTreeWithArena(depth-1, a)
return node
func (n *Node) itemCheck() int {
if n.left == nil {
return 1
return 1 + n.left.itemCheck() + n.right.itemCheck()
const minDepth = 4
var useArena = flag.Bool("arena", false, "use arena")
func main() {
flag.Parse()
if flag.NArg() > 0 {
n, _ = strconv.Atoi(flag.Arg(0))
appStart := time.Now()
defer func() {
fmt.Printf("benchmark took: %v\n", time.Since(appStart))
if *useArena {
maxDepth := n
if minDepth+2 > n {
maxDepth = minDepth + 2
stretchDepth := maxDepth + 1
a := arena.NewArena()
start := time.Now()
check := bottomUpTreeWithArena(stretchDepth, a).itemCheck()
a.Free()
fmt.Printf("stretch tree of depth %d\t check: %d, took: %v\n", stretchDepth, check, time.Since(start))
a = arena.NewArena()
longLiveStart := time.Now()
longLivedTree := bottomUpTreeWithArena(maxDepth, a)
defer a.Free()
for depth := minDepth; depth <= maxDepth; depth += 2 {
iterations := 1 << uint(maxDepth-depth+minDepth)
check = 0
start := time.Now()
for i := 1; i <= iterations; i++ {
a := arena.NewArena()
check += bottomUpTreeWithArena(depth, a).itemCheck()
a.Free()
fmt.Printf("%d\t trees of depth %d\t check: %d, took: %v\n", iterations, depth, check, time.Since(start))
fmt.Printf("long lived tree of depth %d\t check: %d, took: %v\n", maxDepth, longLivedTree.itemCheck(), time.Since(longLiveStart))
} else {
maxDepth := n
if minDepth+2 > n {
maxDepth = minDepth + 2
stretchDepth := maxDepth + 1
start := time.Now()
check := bottomUpTree(stretchDepth).itemCheck()
fmt.Printf("stretch tree of depth %d\t check: %d, took: %v\n", stretchDepth, check, time.Since(start))
longLiveStart := time.Now()
longLivedTree := bottomUpTree(maxDepth)
for depth := minDepth; depth <= maxDepth; depth += 2 {
iterations := 1 << uint(maxDepth-depth+minDepth)
check = 0
start := time.Now()
for i := 1; i <= iterations; i++ {
check += bottomUpTree(depth).itemCheck()
fmt.Printf("%d\t trees of depth %d\t check: %d, took: %v\n", iterations, depth, check, time.Since(start))
fmt.Printf("long lived tree of depth %d\t check: %d, took: %v\n", maxDepth, longLivedTree.itemCheck(), time.Since(longLiveStart))
这段程序中我们使用-arena
参数控制要不要使用arena
。首先你必须安装或者更新gotip
到最新版(如果你已经安装了gotip, 执行gotip downloamd
,如果还未安装,请先go install golang.org/dl/gotip@latest
)。
- 启用
-arena
: 运行GOEXPERIMENT=arenas gotip run -arena main.go 21
- 不启用
-arena
: 运行GOEXPERIMENT=arenas gotip run -arena=false main.go 21
不过这个特性还在开发之中,功能还不完善。
我在MacOS上测试,使用arena
性能会有明显的提升,而在windows下测试,性能反而下降了。
Recommend
-
43
酒店特惠:情之所起 入心为诗 杭州情诗酒店住宿套餐 爱不是说出来的,爱是……嘿嘿嘿 829元/晚(券后),来自什么值得买甄选出的京东优惠产品,汇聚数十万什么值得买网友对该网购产品的点评。
-
83
问与答 - @Sanko -
-
60
-
92
本文来自微信公众号:
-
24
MacBook Pro - @aLazarus - 心累,键盘 R 键无法弹起并且连击。 今天
-
64
技术点 本文不是一个吹嘘的文章,不会讲很多高深的架构,相反,会讲解很多基础的问题和写法问题,如果读者自认为基础问题和写法问题都是不是问题,那请忽略这篇文章,节省出时间去做一些有意义的事情。 开发工具 不知道有多少”老”程序员还在使用 Eclipse,这些程序...
-
31
京东 - @huahuacui - 有医用口罩吗?我的囤货快凉凉了。我今天看到一个贴子 1.8 块,我一看时间就知道凉凉下手晚了。苍天啊大地啊!
-
9
文章有点长,请各位看官按下耐心,一定看下去,虽然数据库这块的内容很枯燥,但是一定得保证自己全部都掌握,才能拿到一个很好的Offer,不是么? 大部分人说的SQL优化 阿粉之前帮公司面试过一部分人,因为之前和老大一起面试,...
-
3
java中接口一般都是不采用任何修饰符默认default,为啥其他包的类还能调用呢?不是说 default修饰的同包才有访问权限吗?
-
8
导读:下拉菜单相关应用在平常的交互设计当中是少不了的一环,也是被用户饱受批评的重灾区。设计得不好会适得其反,让它变成繁琐与LOW的代名词。这篇文章我们也跟上一篇radio button一样,从根本上来分析下拉菜单的构造、应用场景、注意点。
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK