go-gost / gost

GO Simple Tunnel - a simple tunnel written in golang
https://gost.run
MIT License
4.39k stars 528 forks source link

当前sync.Pool的用法可能不是最优的 #409

Closed happyharryh closed 6 months ago

happyharryh commented 9 months ago

刚看了一篇文章写如何正确使用sync.Pool提升slice的性能,结论是推荐使用指向slice的指针。

我的理解:使用slice本体版本的sync.Pool(当前gost的使用方式)其实已经可以节省新建slice时为底层数组申请空间和初始化的开销了。但slice的头结构(共24字节)会在被Put时从栈空间逃逸进入堆空间,造成这24字节的内存不可被立即释放或复用,只能等待runtime的下一次自动垃圾回收。而使用指向slice的指针就可以避免这个问题。

我用三种方案跑了一下Benchmark,的确有差别。目前我对sync.Pool的了解还不是很深入,所以还需大佬确认。

pool_test.go

package pool

import (
    "sync"
    "testing"
)

//go:noinline
func doSomething(buf []byte) {
    buf[0] = 123
}

func BenchmarkRaw(b *testing.B) {
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        buf := make([]byte, 4096) // go优化后不需要每次申请空间,但需要对空间初始化
        doSomething(buf)
    }
}

// 参考: https://github.com/go-gost/core/blob/master/common/bufpool/pool.go
func BenchmarkGost(b *testing.B) {
    pool := sync.Pool{
        New: func() interface{} {
            buf := make([]byte, 4096)
            return buf
        },
    }

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        buf := pool.Get().([]byte) // 每次都需要为buf(`slice`的头结构)申请空间
        doSomething(buf)
        pool.Put(buf) // 底层数组被回收,但`slice`的头结构由栈入堆,不可被复用
    }
}

// 参考: https://blog.mike.norgate.xyz/unlocking-go-slice-performance-navigating-sync-pool-for-enhanced-efficiency-7cb63b0b453e
func BenchmarkRecommend(b *testing.B) {
    pool := sync.Pool{
        New: func() interface{} {
            buf := make([]byte, 4096)
            return &buf
        },
    }

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        pBuf := pool.Get().(*[]byte)
        doSomething(*pBuf)
        pool.Put(pBuf)
    }
}

测试结果

$ go test -bench . -benchmem
goos: darwin
goarch: arm64
pkg: pool
BenchmarkRaw-8          13387327            89.52 ns/op        0 B/op          0 allocs/op
BenchmarkGost-8         42734725            27.74 ns/op       24 B/op          1 allocs/op
BenchmarkRecommend-8    139212199            8.516 ns/op           0 B/op          0 allocs/op
PASS
ok      pool    5.762s
UallenQbit commented 9 months ago

是的,确实不是最优的 目前最高性能的是 sing 13ns/op

ginuerzh commented 8 months ago

之前的版本中采用的是slice指针方式,但从代码的可读性和使用上的便利性方面考虑最终放弃了。