golang gc/arch 對 benchmark 的影響

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

最近在同事提出了一個疑問:在對一個slice進行遍曆時,將for迴圈條件中的len提出到迴圈外是否會比golang編譯器的最佳化結果更加好。

即:

func g0(a []int) int {    l := len(a)    for i := 0; i < l; i++ {    }    return 1}

是否會比

func g1(a []int) int {    for i := 0; i < len(a); i++ {    }    return 1}

的結果更加最佳化(目前golang的編譯器並不會對這個空迴圈進行消除)。

那為了證明這個問題,那就上 benchmark 證明啊。

import "testing"var a = make([]int, 1<<25)func BenchmarkG0(b *testing.B) {    for i := 0; i < b.N; i++ {        g0(a)    }}func BenchmarkG1(b *testing.B) {    for i := 0; i < b.N; i++ {        g1(a)    }}

然後執行

go test -c ../len.test -test.bench=. -test.count=2

得到輸出結果:

goos: darwingoarch: amd64BenchmarkG0-4            100      11784627 ns/opBenchmarkG0-4            100      11841061 ns/opBenchmarkG1-4            100      18623122 ns/opBenchmarkG1-4            100      17790754 ns/opPASS

果然g0g1速度快很多,但是這個有點反常識啊,不能這樣輕易下定結論。那我們來看看g0的編譯結果是否就比g1最佳化很多:

我們來執行

go tool objdump ./len.test > main.s

我們來看得到的結果:

TEXT _/test/go/len.g0(SB) /test/go/len/main.go  main.go:4             0x10ef150               488b442410              MOVQ 0x10(SP), AX  main.go:4             0x10ef155               31c9                    XORL CX, CX  main.go:6             0x10ef157               eb03                    JMP 0x10ef15c  main.go:6             0x10ef159               48ffc1                  INCQ CX  main.go:6             0x10ef15c               4839c1                  CMPQ AX, CX  main.go:6             0x10ef15f               7cf8                    JL 0x10ef159  main.go:8             0x10ef161               48c744242001000000      MOVQ $0x1, 0x20(SP)  main.go:8             0x10ef16a               c3                      RET  :-1                   0x10ef16b               cc                      INT $0x3  :-1                   0x10ef16c               cc                      INT $0x3  :-1                   0x10ef16d               cc                      INT $0x3  :-1                   0x10ef16e               cc                      INT $0x3  :-1                   0x10ef16f               cc                      INT $0x3TEXT _/test/go/len.g1(SB) /test/go/len/main.go  main.go:12            0x10ef170               488b442410              MOVQ 0x10(SP), AX  main.go:12            0x10ef175               31c9                    XORL CX, CX  main.go:13            0x10ef177               eb03                    JMP 0x10ef17c  main.go:13            0x10ef179               48ffc1                  INCQ CX  main.go:13            0x10ef17c               4839c1                  CMPQ AX, CX  main.go:13            0x10ef17f               7cf8                    JL 0x10ef179  main.go:15            0x10ef181               48c744242001000000      MOVQ $0x1, 0x20(SP)  main.go:15            0x10ef18a               c3                      RET  :-1                   0x10ef18b               cc                      INT $0x3  :-1                   0x10ef18c               cc                      INT $0x3  :-1                   0x10ef18d               cc                      INT $0x3  :-1                   0x10ef18e               cc                      INT $0x3  :-1                   0x10ef18f               cc                      INT $0x3

我們可以看到編譯器產生的中間代碼完全是相同的,那為什麼在運行起來會有不同的結果呢?那我們就要考慮了,除了代碼以外,還有什麼會影響代碼執行?那就是:

  • 運行環境
  • runtime

那我們就分別對這兩個因素進行驗證。

首先是運行環境,我們換到linux上再進行一次驗證:

GOOS=linux GOARCH=amd64 go test -c .## copy to linux./test.len -test.bench=. -test.count=2

得到輸出結果:

goos: linuxgoarch: amd64BenchmarkG0-32             100      10824437 ns/opBenchmarkG0-32             100      10743979 ns/opBenchmarkG1-32             100      10740347 ns/opBenchmarkG1-32             100      10898047 ns/opPASS

linuxg0/g1的表現是相同的。那我們就要考慮了linuxdarwin有哪些不同?這個可就多了,沒有辦法去一一對比了。但是這些區別會很大程度反映到runtime上。

那我們就對runtime進行比較。那runtime中的什麼會影響到程式的運行?可能有:(未列舉全)

  • 函數棧空間的擴充
  • goroutine的調度
  • io/syscall/cgo
  • gc

我們從上面的objdump的結果來看,產生的程式碼應該跟前3個因素都無關。那我們就嘗試關閉gc,再進行一次比較:

import (    "runtime/debug"    "testing")func init() {    debug.SetGCPercent(-1)}var a = make([]int, 1<<25)func BenchmarkG0(b *testing.B) {    for i := 0; i < b.N; i++ {        g0(a)    }}func BenchmarkG1(b *testing.B) {    for i := 0; i < b.N; i++ {        g1(a)    }}

然後

go test -c ../len.test -test.bench=. -test.count=2

得到結果:

goos: darwingoarch: amd64BenchmarkG0-4            100      11521770 ns/opBenchmarkG0-4            100      11310217 ns/opBenchmarkG1-4            100      11562763 ns/opBenchmarkG1-4            100      11590019 ns/opPASS

可以看出關閉gc後,g0/g1表現相同,那是因為g1在運行過程中會動態分配記憶體嗎?上面objdump的結果來看,明顯不是。那隻有可能是gc啟動並執行時機問題,為什麼gc偏巧要在g1運行時啟動呢?這個就比較微妙了,runtime的表現跟系統很多因素有關係,相同的代碼在不同的作業系統上也有微妙的差異,或許有某種偽隨機的因素在runtime中?還未求證。

經過再次驗證這種GC的影響與Benchmark函數的前後位置無關。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.