<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>

          再談Golang內(nèi)存對(duì)齊

          共 4125字,需瀏覽 9分鐘

           ·

          2022-05-27 19:19

          關(guān)于 Golang 內(nèi)存對(duì)齊,之前寫了一篇「淺談Golang內(nèi)存對(duì)齊[1]」,可惜對(duì)一些細(xì)節(jié)問(wèn)題的討論語(yǔ)焉不詳,于是便有了今天這篇「再談Golang內(nèi)存對(duì)齊」。

          讓我們回想一下 groupcache[2]sync.WaitGroup[3] 中的做法,為了規(guī)避在 32 位環(huán)境下 atomic 操作 64 位數(shù)的 BUG,它們采取了截然不同的做法:

          //?groupcache
          type?Group?struct?{
          ?name?string
          ?getter?Getter
          ?peersOnce?sync.Once
          ?peers?PeerPicker
          ?cacheBytes?int64
          ?mainCache?cache
          ?hotCache?cache
          ?loadGroup?flightGroup
          ?_?int32?//?force?Stats?to?be?8-byte?aligned?on?32-bit?platforms
          ?Stats?Stats
          }

          //?sync.WaitGroup
          type?WaitGroup?struct?{
          ?noCopy?noCopy

          ?//?64-bit?value:?high?32?bits?are?counter,?low?32?bits?are?waiter?count.
          ?//?64-bit?atomic?operations?require?64-bit?alignment,?but?32-bit
          ?//?compilers?do?not?ensure?it.?So?we?allocate?12?bytes?and?then?use
          ?//?the?aligned?8?bytes?in?them?as?state,?and?the?other?4?as?storage
          ?//?for?the?sema.
          ?state1?[3]uint32
          }

          func?(wg?*WaitGroup)?state()?(statep?*uint64,?semap?*uint32)?{
          ?if?uintptr(unsafe.Pointer(&wg.state1))%8?==?0?{
          ??return?(*uint64)(unsafe.Pointer(&wg.state1)),?&wg.state1[2]
          ?}?else?{
          ??return?(*uint64)(unsafe.Pointer(&wg.state1[1])),?&wg.state1[0]
          ?}
          }

          問(wèn)題:為什么 groupcache 不用考慮外部地址,只要內(nèi)部對(duì)齊就可以實(shí)現(xiàn) 64 位對(duì)齊?

          為了搞清楚這個(gè)問(wèn)題,讓我們回想一下 atomic[4] 文檔最后關(guān)于 64 位對(duì)齊的相關(guān)描述:

          On ARM, 386, and 32-bit MIPS, it is the caller’s responsibility to arrange for 64-bit alignment of 64-bit words accessed atomically. The first word in a variable or in an allocated struct, array, or slice can be relied upon to be 64-bit aligned.

          其中我們關(guān)心的是最后一句話:變量、結(jié)構(gòu)體、數(shù)組、切片的第一個(gè)字是 64 位對(duì)齊的。為了驗(yàn)證這一點(diǎn),我構(gòu)造了一個(gè)包含 int64 的 struct,看它的地址是否是 8 的倍數(shù):

          package?main

          import?(
          ?"fmt"
          ?"time"
          ?"unsafe"
          )

          type?foo?struct?{
          ?bar?int64
          }

          //?GOARCH=386?go?run?main.go
          func?main()?{
          ?for?range?time.Tick(time.Second)?{
          ??f?:=?&foo{}
          ??p?:=?uintptr(unsafe.Pointer(f))
          ??fmt.Printf("%p:?%d,?%d\n",?f,?p,?p%8)
          ?}
          }

          按照常理來(lái)說(shuō),當(dāng)我們?cè)?32 位環(huán)境(GOARCH=386)下運(yùn)行的時(shí)候,struct 的地址應(yīng)該只能滿足 32 位對(duì)齊,也就是 4 的倍數(shù),不過(guò)測(cè)試發(fā)現(xiàn),當(dāng) struct 里含有 int64 的時(shí)候,struct 的地址竟然滿足 64 位對(duì)齊,也就是是 8 的倍數(shù)。既然外部已經(jīng)是對(duì)齊的了,那么只要內(nèi)部對(duì)齊就可以實(shí)現(xiàn) 64 位對(duì)齊。

          問(wèn)題:為什么 sync.WaitGroup 不像 groupcache 那樣實(shí)現(xiàn) 64 位對(duì)齊。

          兩者之所以采用了不同的 64 位對(duì)齊實(shí)現(xiàn)方式,是因?yàn)閮烧叩氖褂脠?chǎng)景不同。在實(shí)際使用的時(shí)候,sync.WaitGroup 可能會(huì)被嵌入到別的 struct 中,因?yàn)椴恢狼度氲木唧w位置,所以不可能通過(guò)預(yù)先加入 padding 的方式來(lái)實(shí)現(xiàn) 64 位對(duì)齊,只能在運(yùn)行時(shí)動(dòng)態(tài)計(jì)算。而 groupcache 則不會(huì)被嵌入到別的 struct 中,如果你硬要嵌入,可能會(huì)出問(wèn)題:

          package?main

          import?(
          ?"github.com/golang/groupcache"
          )

          type?foo?struct?{
          ?bar?int32
          ?g?groupcache.Group
          }

          //?GOARCH=386?go?run?main.go
          func?main()?{
          ?f?:=?foo{}
          ?f.g.Stats.Gets.Add(1)
          }

          當(dāng)我們?cè)?32 位環(huán)境(GOARCH=386)下運(yùn)行的時(shí)候,會(huì)看到如下 panic 信息:

          panic: unaligned 64-bit atomic operation

          當(dāng)我們?cè)?32 位環(huán)境,按 4 字節(jié)對(duì)齊,所以 g 的偏移量是 4 而不是 8,如此一來(lái),雖然 groupcache 內(nèi)部通過(guò) _ int32 實(shí)現(xiàn)了相對(duì)的 64 位對(duì)齊,但是因?yàn)橥獠繘](méi)有實(shí)現(xiàn) 64 位對(duì)齊,所以在執(zhí)行 atomic 操作的時(shí)候,還是會(huì) panic(如果 bar 是 int64 就不會(huì) panic)。

          問(wèn)題:為什么 sync.WaitGroup 中的 state1 不換成 一個(gè) int64 加一個(gè) int32?

          我們知道 sync.WaitGroup 中的 state1 字段是一個(gè)有 3 個(gè)元素的 uint32 數(shù)組,它會(huì)保存兩種數(shù)據(jù),分別是 statep 和 semap,相當(dāng)于一個(gè)是 int64,另一個(gè)是 int32。那為什么它不直接把一個(gè) state1 字段替換成兩個(gè)獨(dú)立的字段,一個(gè) int64 加一個(gè) int32。

          當(dāng)然可以換,但是因?yàn)?sync.WaitGroup 可能會(huì)被嵌入到別的 struct 中,并且不知道嵌入的具體位置,所以還是需要在運(yùn)行時(shí)動(dòng)態(tài)計(jì)算是否要 padding,并且這個(gè) padding 的工作要額外空間來(lái)承擔(dān),不能被獨(dú)立的 int32 兼任。和原方案比無(wú)疑浪費(fèi)了空間。

          問(wèn)題:為什么 sync.WaitGroup 中的 state1 不換成 一個(gè)[12]byte?

          原方案中 state1 的類型是 [3]uint32,取兩個(gè) uint32 做 statep,剩下的一個(gè) uint32 做 semap。為什么不換成 [12]byte,取 8 個(gè) byte 做 statep,剩下的 4 個(gè) byte 做 semap?

          想要搞清楚這個(gè)問(wèn)題,我們想要回顧一下 golang 關(guān)于內(nèi)存對(duì)齊保證的描述:

          • For a variable x of any type: unsafe.Alignof(x) is at least 1.
          • For a variable x of struct type: unsafe.Alignof(x) is the largest of all the values unsafe.Alignof(x.f) for each field f of x, but at least 1.
          • For a variable x of array type: unsafe.Alignof(x) is the same as the alignment of a variable of the array’s element type.

          其中的重點(diǎn)是:對(duì) struct 而言,它的對(duì)齊取決于其中所有字段對(duì)齊的最大值;對(duì)于 array 而言,它的對(duì)齊等于元素類型本身的對(duì)齊。因?yàn)?noCopy 的大小是 0,所以 struct 的對(duì)齊實(shí)際上就取決于 state1 字段的對(duì)齊。

          • 當(dāng) state1 的類型是 [3]uint32 的時(shí)候,那么 struct 的對(duì)齊就是 4。
          • 當(dāng) state1 的類型是 [12]byte 的時(shí)候,那么 struct 的對(duì)齊就是 1。

          如果 state1 換成 [12]byte,那么因?yàn)?struct 的對(duì)齊是 1,會(huì)導(dǎo)致 struct 的地址不再是 4 的倍數(shù),結(jié)果 uintptr(unsafe.Pointer(&wg.state1))%8 == 0 的判斷也就沒(méi)有意義了。

          參考資料

          [1]

          淺談Golang內(nèi)存對(duì)齊: https://blog.huoding.com/2021/09/29/951

          [2]

          groupcache: https://github.com/golang/groupcache/blob/master/groupcache.go

          [3]

          sync.WaitGroup: https://github.com/golang/go/blob/master/src/sync/waitgroup.go

          [4]

          atomic: https://pkg.go.dev/sync/atomic



          推薦閱讀


          福利

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

          瀏覽 148
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  欧美在线视频一区 | e133日韩无码 | 日韩少妇内射 | 在线播放高清无码 | 琪琪色影音 |