go性能優(yōu)化

go性能優(yōu)化

寄存器結(jié)構(gòu)

  1. cache的最小存儲(chǔ)單位為cache line,一個(gè)cache line 64字節(jié),如果只從內(nèi)存中讀取一個(gè)字節(jié),也會(huì)將cache line的64字節(jié)都加載到cache中
  2. perf進(jìn)行性能分析 perf record -F 1999 -e cycles,cache-misses,vranch-misses --pid xx -o x
    • -F指定采樣頻率
    • -e 指定采集的信息,cycles為cpu周期,cache misses 緩存miss,branch misses 分支miss
  3. cpu緩存:時(shí)間局部性(頻繁訪問(wèn)的緩存)和空間局部性(訪問(wèn)的空間和附近的都加載)

cache line 優(yōu)化

RunParallel創(chuàng)建多個(gè)goroutinue然后把b.N個(gè)迭代測(cè)試分布到這些goroutine上。goroutinue的數(shù)目默認(rèn)是GOMAXPROCS,如果要增加non-COU-bound的benchmark的并發(fā)個(gè)數(shù),在執(zhí)行RunParallel之前調(diào)用SetParallelism

type NoPad struct {
    a uint64
    // l uint64[7]
    b uint64
}

func (p *NoPad) add(){
    atomic.AddUint64(&p.a,1)
}

func (p *NoPad) read(){
    return atomic.LoadUint64(&p.b)
}

func BenchmarkNoPad_In(b *testing.B){
    noPad := &NoPad{}
    var r uint64
    b.RunParallel(func(pb *testing.PB){
        gor pb.Next() {
            for i:=0;i<1<<10;i++{
                noPad.add()
                r = noPad.read()
            }
        }
    })
}

上面代碼,不加注釋行的話,a和b會(huì)加載到同一個(gè)cache line,如果多個(gè)cpu并行執(zhí)行,例如cpu0對(duì)a進(jìn)行寫(xiě)操作,cpu1執(zhí)行了對(duì)b的讀操作,cpu0對(duì)a寫(xiě)完會(huì)將整個(gè)struct寫(xiě)入內(nèi)存,計(jì)算機(jī)不知道程序?qū)沒(méi)有修改,導(dǎo)致cpu1會(huì)重新加載b,從而導(dǎo)致cache miss,性能更差,這稱為偽共享

運(yùn)行時(shí)優(yōu)化

  1. go包含工具、編譯器、運(yùn)行時(shí)、標(biāo)準(zhǔn)庫(kù)

gc和內(nèi)存申請(qǐng)優(yōu)化

  1. 減少對(duì)象申請(qǐng),增加對(duì)象復(fù)用,減少對(duì)象逃逸,減少不必要對(duì)象申請(qǐng)
  2. 延長(zhǎng)gc間隔時(shí)間
  3. 減少gc時(shí)長(zhǎng),減少對(duì)象掃描時(shí)間、常駐內(nèi)存通過(guò)cgo申請(qǐng)

pprof工具使用

  1. pprof進(jìn)行性能分析,有輕度代碼侵入
  2. pprof常用命令
    • allocs,查看所有內(nèi)存分配樣本
    • block,查看導(dǎo)致阻塞的同步堆棧跟蹤
    • goroutine 查看所有運(yùn)行的goroutine堆棧跟蹤
    • heap,查看活動(dòng)對(duì)象的內(nèi)存分配情況
    • profile 默認(rèn)進(jìn)行30s的cpu 性能采集,生成數(shù)據(jù)文件
    • curl http://xx/debug/pprof/xx > d.data 獲取數(shù)據(jù)
    • go tool pprof -http=xxx d.data 展示數(shù)據(jù)

減少對(duì)象申請(qǐng)

// sync.pool 使用
package main
import (
    "fmt"
    "sync"
)
var pool *sync.Pool
type Person struct {
    Name string
}
func initPool() {
    pool = &sync.Pool {
        New: func()interface{} {
            fmt.Println("Creating a new Person")
            return new(Person)
        },
    }
}
func main() {
    initPool()
    p := pool.Get().(*Person)
    fmt.Println("首次從 pool 里獲取:", p)
    p.Name = "first"
    fmt.Printf("設(shè)置 p.Name = %s\n", p.Name)
    pool.Put(p)
    fmt.Println("Pool 里已有一個(gè)對(duì)象:&{first},調(diào)用 Get: ", pool.Get().(*Person))
    fmt.Println("Pool 沒(méi)有對(duì)象了,調(diào)用 Get: ", pool.Get().(*Person))
}
  1. 使用sync.pool進(jìn)行對(duì)象復(fù)用
// 每次創(chuàng)建對(duì)象, 每次都需要malloc內(nèi)存,然后通過(guò)gc釋放
func process() {
    for n:=0;n<1<<10;n++ {
        stu := &Persion{}
        json.Unmarshal(buf,stu)
    }
}

// 復(fù)用對(duì)象,從池子里獲取對(duì)象,池子里沒(méi)有才申請(qǐng)內(nèi)存
func process() {
    for n := 0; n< 1<<10; n++ {
        stu := pool.Get().(*Person)
        json.Unmarshal(buf,stu)
        pool.Put(stu)
    }
}

延長(zhǎng)gc時(shí)間

  1. runtime.GC() 手工執(zhí)行g(shù)c
  2. debug.SetGCPercent(150) 設(shè)置gc系數(shù)
runtime.GC()
N := 100
for {
    // 每次申請(qǐng)100m內(nèi)存
    produceTemporaryObject(N)  
    time.Sleep(time.Millsecond * 10)
}


// 設(shè)置gc
debug.SetGCPercent(150)
runtime.GC()
N := 100
for {
    // 每次申請(qǐng)100m內(nèi)存
    produceTemporaryObject(N)  
    time.Sleep(time.Millsecond * 10)
}
  1. 觸發(fā)gc閾值的計(jì)算公式,gc_trigger = 上次標(biāo)記存活內(nèi)存量 * (1 + gogc/100) gogc默認(rèn)100
  2. gc時(shí)間間隔不是越長(zhǎng)越好,如果臨時(shí)對(duì)象很多,程序gc時(shí)間太長(zhǎng)反而無(wú)法及時(shí)回收內(nèi)存

減少對(duì)象掃描時(shí)間

type person struct {
    age int
    sex [200]int
    name string
}

func newPerson() *person{
    obj := new(person)
    obj.name ="x"
    return obj
}


// 這樣定義性能更高,定義結(jié)構(gòu)體時(shí),指針類(lèi)型字段往前放
type person2 struct {
    name string 
    age int
    sex [200]int
}

在掃描結(jié)構(gòu)體內(nèi)存時(shí),掃描到最后一個(gè)指針就停止掃描了,在go結(jié)構(gòu)中,會(huì)存儲(chǔ)每個(gè)指針的位置和最后一個(gè)指針的位置,后面的就不在進(jìn)行掃描

協(xié)程優(yōu)化

  1. 限制協(xié)程數(shù)量
// 通過(guò)帶緩存channel限制
var wg sync.WaitGroup

var ch = make(chan struct{},10)
for i:=0;i<iteration;i++{
    ch <- struct{} // 池滿了會(huì)阻塞
    wg.Add(1)
    go func(i int){
        defer wg.Done()
        fmt.Println(i)
        time.Sleep(time.Second)
        <-ch
    }{i}
}
wg.Wait() 
  1. 使用協(xié)程池https://blog.csdn.net/K346K346/article/details/104370501?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167627276116800213093960%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=167627276116800213093960&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allbaidu_landing_v2~default-4-104370501-null-null.142v73insert_down4,201v4add_ask,239v1control&utm_term=go%20%E5%8D%8F%E7%A8%8B%E6%B1%A0&spm=1018.2226.3001.4187

語(yǔ)言特性優(yōu)化

  1. 頻繁解引用
// 每次循環(huán)都是先解開(kāi)引用,然后在轉(zhuǎn)換成指針,效率低
func a(sum *int, s []int){
    for _,v:=range s {
        *sum += v
    }
}

// 優(yōu)化
func b(sum *int,s []int) {
    var n = *num;
    for _,v :=range s {
        n += v
    }
    *num = n
}
// 結(jié)構(gòu)體同樣
func c(a *T){
    // n := *T
    for 1:=0;i<1000;i++{
        //n.x += i
        a.x += i
    }
}
  1. 減少值拷貝
// 每次調(diào)用都要值傳遞xy
func add(x,y[1000]int) int {
    return x[0]+y[0]
}
// 改成引用傳值
func add1(x,y *[1000]int)int {
    return (*x)[0]+(*y)[0]
}
  1. 使用小結(jié)構(gòu)體,小結(jié)構(gòu)體在go源碼中有優(yōu)化,速度更快,建議使用值拷貝,而不使用引用傳值,少于16字節(jié)為小結(jié)構(gòu)體
  2. 在已有數(shù)組或者切片上reslice,其實(shí)是引用,如果原數(shù)組較大,只引用了一部分進(jìn)行操作,會(huì)導(dǎo)致內(nèi)存得不到釋放,建議使用拷貝
func copy(s [512]byte)[]byte{
    newSlice := make([]byte,2)
    copy(newSlice,s[0:2])
    return newSlice
}
  1. 切片反復(fù)擴(kuò)容,小于1024兩倍擴(kuò)容,大于1024 1/4擴(kuò)容,make指定cap時(shí),如果是變量,則會(huì)分配在堆上,是常量并且數(shù)組大小小于64k,則在棧上
// 不推薦,不指定大小 arr := make([]int,0) 
// 不推薦,指定大小為變量,會(huì)發(fā)生一次alloc arr := make([]int,0,n)
arr := make([]int,0,100)
  1. for range使用指針,如果使用非指針,會(huì)頻繁發(fā)生value的值拷貝,for range的value項(xiàng)不使用指針會(huì)發(fā)生值拷貝,影響性能,只是用下標(biāo)和for循環(huán)效率基本一致
var persons [1024]*person
var totalAge int

for _,persion := range persions {
    totalAge += person.age
}
  1. 清空一個(gè)數(shù)組時(shí),使用for range
for i:=range arr {
    arr[i]=zeroValue
}
  1. 字符串拼接,不建議用+=和fmt.Sprintf, 使用bytes builder和string builder, strings.repeat也可以
func bufferConcat(n int, str string) string {
    buf := new(bytes.Buffer)
    buf.Grow(n* len(str)) // 字符串總長(zhǎng)度

    for i:=0;i<n;i++ {
        buf.WriteString(str)
    }
    return buf.String()
}

func bufferConcat1(n int, str string) string {
    bar builder strings.Builder
    builder.Grow(n* len(str)) // 字符串總長(zhǎng)度

    for i:=0;i<n;i++ {
        builder.WriteString(str)
    }
    return builder.String()
}
  1. map刪除元素,如果使用delete關(guān)鍵字,不會(huì)回收內(nèi)存,make重建會(huì)gc內(nèi)存,但是慢
  2. 將map轉(zhuǎn)換成一個(gè)枚舉類(lèi)型加一個(gè)數(shù)組,下標(biāo)的映射使用枚舉形成
  3. 使用接口斷言成結(jié)構(gòu)體或者是使用結(jié)構(gòu)體進(jìn)行函數(shù)調(diào)用比直接使用接口調(diào)用要快,但是不多
func (p mystruct) test()
type myin interface{
    test()
}

func BenchmarkA (b *testing.B){
    //var p1 myin = mystruct{}
    //p1.test();
    // p myin = &mystruct{}
    // p.(*mystruct).test()

    var p2 = mystruct{}
    for i:=0;i<b.N;i++ {
        p2.test()
    }
}
  1. 接口斷言成結(jié)構(gòu)體性能非常高
  2. 少使用閉包函數(shù)和defer

并發(fā)場(chǎng)景

  1. map只有對(duì)key操作才使用寫(xiě)鎖
  2. 繁殖使用defer導(dǎo)致鎖定的代碼段增加
  3. 減少鎖范圍,例如可以對(duì)一個(gè)map的不同段加不同的鎖
  4. 使用原子操作代替鎖,原子操作指執(zhí)行過(guò)程中不能被中斷的操作,cpu不會(huì)執(zhí)行其他對(duì)該值得操作
  5. 減少select 的case數(shù)量,越少cpu開(kāi)銷(xiāo)越小
  6. case中receive比send效率高
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容