<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          某些情況下,合理使用Go指針將大大提升程序的運(yùn)行效率

          共 4223字,需瀏覽 9分鐘

           ·

          2022-06-09 05:38

          1. 避免在循環(huán)中造成不必要的數(shù)組空指針檢查


          目前官方標(biāo)準(zhǔn) Go 編譯器實(shí)現(xiàn)中存在一些缺陷(v1.18)。其中之一是?一些 nil 數(shù)組指針檢查沒有被移出循環(huán)。這里有一個例子來體現(xiàn)這個缺陷。

          // unnecessary-checks.gopackage pointers
          import "testing"
          const N = 1000var a [N]int
          //go:noinlinefunc g0(a *[N]int) { for i := range a { a[i] = i // line 12 }}
          //go:noinlinefunc g1(a *[N]int) { _ = *a // line 18 for i := range a { a[i] = i // line 20 }}
          func Benchmark_g0(b *testing.B) { for i := 0; i < b.N; i++ { g0(&a) }}
          func Benchmark_g1(b *testing.B) { for i := 0; i < b.N; i++ { g1(&a) }}

          讓我們用?-S?編譯選項(xiàng)來運(yùn)行此基準(zhǔn)測試,得到的輸出結(jié)果如下(省略了不感興趣的輸出):

          $ go test -bench=. -gcflags=-S unnecessary-checks.go...0x0004 00004 (unnecessary-checks.go:12)  TESTB  AL, (AX)0x0006 00006 (unnecessary-checks.go:12)  MOVQ  CX, (AX)(CX*8)...0x0000 00000 (unnecessary-checks.go:18)  TESTB  AL, (AX)0x0002 00002 (unnecessary-checks.go:18)  XORL  CX, CX0x0004 00004 (unnecessary-checks.go:19)  JMP  130x0006 00006 (unnecessary-checks.go:20)  MOVQ  CX, (AX)(CX*8)...Benchmark_g0-4  517.6 ns/opBenchmark_g1-4  398.1 ns/op

          從輸出結(jié)果中,我們可以發(fā)現(xiàn)?g1?實(shí)現(xiàn)比?g0?實(shí)現(xiàn)更高效。即使?g1?的實(shí)現(xiàn)多了一行代碼(第 18 行)。為什么會這樣?輸出的匯編指令回答了這個問題。


          在?g0?實(shí)現(xiàn)中,TESTB?指令生成在在循環(huán)內(nèi),而在?g1?實(shí)現(xiàn)中,TESTB?指令生成在循環(huán)外。?TESTB?指令用于檢查參數(shù)?a?是否是一個空指針。對于這種特定情況,檢查一次就足夠了。多出來的這一行代碼避免了編譯器實(shí)現(xiàn)中的缺陷。


          這里有第三種實(shí)現(xiàn),其性能與?g1?的實(shí)現(xiàn)一樣高效。第三種實(shí)現(xiàn)方式使用了一個從數(shù)組指針參數(shù)派生出來的切片。

          //go:noinlinefunc g2(x *[N]int) {  a := x[:]  for i := range a {    a[i] = i  }}

          請注意該缺陷可能在未來的編譯器版本中被修補(bǔ)。


          同時請注意,如果這三個函數(shù)實(shí)現(xiàn)可以內(nèi)聯(lián),那么基準(zhǔn)測試結(jié)果將產(chǎn)生很大變化。這就是為什么這里使用?//go:noinline?編譯器指示的原因。(然而,我們應(yīng)該知道的是,在Go 工具鏈 v1.18 之前,//go:noinline?編譯器指示在這里實(shí)際上是不必要的,因?yàn)?包含?for-range?循環(huán)的函數(shù)從 Go 工具鏈 v1.18 以前是不可內(nèi)內(nèi)聯(lián)的)。


          2. 數(shù)組指針是一個結(jié)構(gòu)體字段的情況


          如果一個數(shù)組指針為一個結(jié)構(gòu)體字段的情況,情況會稍微有點(diǎn)復(fù)雜。下面代碼中的?_ = *t.a?一行無法避開上述編譯器缺陷。例如,在下面的代碼中,f1?函數(shù)和?f0?函數(shù)的性能差異很小。(事實(shí)上,如果在?f1?函數(shù)的循環(huán)內(nèi)產(chǎn)生了一條?NOP?指令,那它可能更慢。)

          type T struct {  a *[N]int}
          //go:noinlinefunc f0(t *T) { for i := range t.a { t.a[i] = i }}
          //go:noinlinefunc f1(t *T) { _ = *t.a for i := range t.a { t.a[i] = i }}

          欲將數(shù)組空指針檢查移出循環(huán),

          我們應(yīng)該把?t.a?字段復(fù)制到一個局部變量,然后采用上面介紹的技巧:

          //go:noinlinefunc f3(t *T) {  a := t.a  _ = *a  for i := range a {    a[i] = i  }}

          或者簡單地從數(shù)組指針字段中派生出一個切片:

          //go:noinlinefunc f4(t *T) {  a := t.a[:]  for i := range a {    a[i] = i  }}

          基準(zhǔn)測試結(jié)果:

          Benchmark_f0-4  622.9 ns/opBenchmark_f1-4  637.4 ns/opBenchmark_f2-4  511.3 ns/opBenchmark_f3-4  390.1 ns/opBenchmark_f4-4  387.6 ns/op

          基準(zhǔn)結(jié)果驗(yàn)證了我們上面的結(jié)論。


          注意,基準(zhǔn)結(jié)果中提到的?f2?函數(shù)聲明為

          //go:noinlinefunc f2(t *T) {  a := t.a  for i := range a {    a[i] = i  }}

          f2?實(shí)現(xiàn)沒有?f3?和?f4?實(shí)現(xiàn)快,但它比?f0?和?f1?實(shí)現(xiàn)快。不過,那是?另一個故事。

          如果數(shù)組指針字段的元素在循環(huán)中不被修改(而僅被讀取),那么?f1?實(shí)現(xiàn)與?f3?和?f4?實(shí)現(xiàn)性能相當(dāng)。


          我的個人觀點(diǎn)是,對于大多數(shù)情況,我們應(yīng)該嘗試使用切片方式(?f4?實(shí)現(xiàn))來獲得最佳性能, 因?yàn)橥ǔ碚f,官方標(biāo)準(zhǔn) Go 編譯器對切片的優(yōu)化要比對數(shù)組的優(yōu)化做得好。


          3. 避免在循環(huán)中進(jìn)行不必要的解引用


          某些時候,當(dāng)前的官方標(biāo)準(zhǔn) Go 編譯器(v1.18)?沒有聰明到以最優(yōu)化的方式生成匯編指令。我們不得不以另一種方式寫代碼以獲得最佳性能。例如,在下面的代碼中,f?函數(shù)的性能比?g?函數(shù)差得多。

          // avoid-indirects_test.gopackage pointers
          import "testing"
          func f(sum *int, s []int) { for _, v := range s { // line 7 *sum += v // line 8 }}
          func g(sum *int, s []int) { var n = 0 for _, v := range s { // line 14 n += v // line 15 } *sum = n}
          var s = make([]int, 1024)var r int
          func Benchmark_f(b *testing.B) { for i := 0; i < b.N; i++ { f(&r, s) }}
          func Benchmark_g(b *testing.B) { for i := 0; i < b.N; i++ { g(&r, s) }}

          基準(zhǔn)測試結(jié)果(省略了不感興趣的文字):

          $ go test -bench=. -gcflags=-S avoid-indirects_test.go...0x0009 00009 (avoid-indirects_test.go:8)  MOVQ  (AX), SI0x000c 00012 (avoid-indirects_test.go:8)  ADDQ  (BX)(DX*8), SI0x0010 00016 (avoid-indirects_test.go:8)  MOVQ  SI, (AX)0x0013 00019 (avoid-indirects_test.go:7)  INCQ  DX0x0016 00022 (avoid-indirects_test.go:7)  CMPQ  CX, DX0x0019 00025 (avoid-indirects_test.go:7)  JGT  9...0x000b 00011 (avoid-indirects_test.go:14)  MOVQ  (BX)(DX*8), DI0x000f 00015 (avoid-indirects_test.go:14)  INCQ  DX0x0012 00018 (avoid-indirects_test.go:15)  ADDQ  DI, SI0x0015 00021 (avoid-indirects_test.go:14)  CMPQ  CX, DX0x0018 00024 (avoid-indirects_test.go:14)  JGT  11...Benchmark_f-4  3024 ns/opBenchmark_g-4   566.6 ns/op

          輸出的匯編指令顯示指針?sum?在?f?函數(shù)的循環(huán)中被解引用。解引用操作是一個內(nèi)存操作。對于?g?函數(shù),解引用操作發(fā)生在循環(huán)外, 而為循環(huán)產(chǎn)生的指令只處理寄存器。CPU 指令處理寄存器的速度比處理內(nèi)存要快得多。這就是為什么?g?函數(shù)比?f?函數(shù)的性能好得多原因。


          對于這種特定情況,另一種高性能實(shí)現(xiàn)是將指針參數(shù)移出函數(shù)體:

          func h(s []int) int {  var n = 0  for _, v := range s {    n += v  }  return n}
          func use_h(s []int) { var sum = new(int) *sum = h(s) ...}


          推薦閱讀


          福利

          我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號 「polarisxu」,回復(fù)?ebook?獲?。贿€可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

          瀏覽 63
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  美女交叉免费视频啪啪 | 3p性爱视频 | 草大逼视频 | 大香蕉大香蕉视频网, | 六月婷婷视频 |