Slide 1

Slide 1 text

※(7/13追記)ブラッシュアップした内容で2018/7/13 のgolang.tokyoでLTしてきたので、そちらのスライド の方をご覧ください! https://speakerdeck.com/kaznishi/180713-lt

Slide 2

Slide 2 text

スライス容量拡張量が どのように決まるのか追った 2018-07-09 Gopher道場 #2 LT大会 by kaznishi

Slide 3

Slide 3 text

自己紹介 twitter: @kaznishi1246 サーバーサイド,インフラ PHP, Scala Go歴はほぼ1ヶ月(≒Gopher道場期間)

Slide 4

Slide 4 text

今回のテーマ

Slide 5

Slide 5 text

スライスの容量がいっぱいの ときにappendで追加される容 量の話

Slide 6

Slide 6 text

復習 スライスは配列の部分列への参照のためのデータ 構造 配列は固定長 容量が足りなくなった場合、容量が拡張された新 たな配列が作られ、参照先が切り替わる

Slide 7

Slide 7 text

容量の拡張量は?

Slide 8

Slide 8 text

容量の拡張量は? 「プログラミング言語Go」より 「Goならわかるシステムプログラミング」より 拡張ごとに配列の大きさを倍にすることにより過 剰な回数の割り当てを避け、一つの要素の追加が 平均的に定数時間で済むことを保証しています。 “ “ もし、余裕がない状態でappend()を呼ぶと、cap() の2倍のメモリを確保し、今までの要素をコピー したうえで新しい要素を新しいメモリ領域に追加 します。 “ “

Slide 9

Slide 9 text

確かめてみよう

Slide 10

Slide 10 text

Go Playgroundで確認 cap = 4 のとき https://play.golang.org/p/zPLWMUM2gzw OK

Slide 11

Slide 11 text

Go Playgroundで確認 cap = 5 のとき https://play.golang.org/p/eyPOVocDUc- 「12」!!!???

Slide 12

Slide 12 text

はて

Slide 13

Slide 13 text

goの実装を追ってみた

Slide 14

Slide 14 text

https://github.com/golang/go/blob/master/src/runti me/slice.go func growslice(et *_type, old slice, cap int) slice { ~略~ newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { if old.len < 1024 { newcap = doublecap } else { // Check 0 < newcap to detect overflow // and prevent an infinite loop. for 0 < newcap && newcap < cap { newcap += newcap / 4 } ~略~

Slide 15

Slide 15 text

~略~ switch { case et.size == 1: lenmem = uintptr(old.len) newlenmem = uintptr(cap) capmem = roundupsize(uintptr(newcap)) overflow = uintptr(newcap) > maxAlloc newcap = int(capmem) case et.size == sys.PtrSize: lenmem = uintptr(old.len) * sys.PtrSize newlenmem = uintptr(cap) * sys.PtrSize capmem = roundupsize(uintptr(newcap) * sys.PtrSize) overflow = uintptr(newcap) > maxAlloc/sys.PtrSize newcap = int(capmem / sys.PtrSize)

Slide 16

Slide 16 text

case isPowerOfTwo(et.size): var shift uintptr if sys.PtrSize == 8 { // Mask shift for better code generation. shift = uintptr(sys.Ctz64(uint64(et.size))) & } else { shift = uintptr(sys.Ctz32(uint32(et.size))) & } lenmem = uintptr(old.len) << shift newlenmem = uintptr(cap) << shift capmem = roundupsize(uintptr(newcap) << shift) overflow = uintptr(newcap) > (maxAlloc >> shift) newcap = int(capmem >> shift) default: lenmem = uintptr(old.len) * et.size newlenmem = uintptr(cap) * et.size capmem = roundupsize(uintptr(newcap) * et.size) overflow = uintptr(newcap) > maxSliceCap(et.size) newcap = int(capmem / et.size) }

Slide 17

Slide 17 text

newcapに調整がかかってる

Slide 18

Slide 18 text

switch { case et.size == sys.PtrSize: ~略~ capmem = roundupsize(uintptr(newcap) * sys.PtrSize) ~略~ newcap = int(capmem / sys.PtrSize) default: ~略~ capmem = roundupsize(uintptr(newcap) * et.size) ~略~ newcap = int(capmem / et.size) } 確保メモリ = roundupsize(補正前スライス容量 x 要素サイズ) 補正後スライス容量 = 確保メモリ / 要素サイズ

Slide 19

Slide 19 text

roundupsize?

Slide 20

Slide 20 text

https://github.com/golang/go/blob/master/src/runti me/msize.go func roundupsize(size uintptr) uintptr { if size < _MaxSmallSize { if size <= smallSizeMax-8 { return uintptr(class_to_size[size_to_class8 [(size+smallSizeDiv-1)/smallSizeDiv]]) } else { return uintptr(class_to_size[size_to_class128 [(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv]]) } } if size+_PageSize < size { return size } return round(size, _PageSize) }

Slide 21

Slide 21 text

roundupsizeというからにはキリの良いところまで メモリの確保量を切り上げているのだろうが、何 のための切り上げ? class_to_size , size_to_class の'class'とは?

Slide 22

Slide 22 text

https://github.com/golang/go/blob/master/src/runti me/sizeclasses.go なんだろうこれは、という感じ

Slide 23

Slide 23 text

「Goならわかるシステムプログラミング」より 小さなオブジェクトについては、より小さな単位 の「クラス」という分類で空きメモリのリストを 持っています。クラスからのメモリ取得では、リ クエストされたサイズに近いクラスの空きリスト があればそこからメモリを確保します。この場合 にはロックが不要であり、それだけ高速に処理で きます。 “ “

Slide 24

Slide 24 text

クラスという分類で空きメモ リリストを管理するため、キ リの良いところまで切り上げ をしている

Slide 25

Slide 25 text

意味が掴めたところでnewcapの計算 結果を確かめてみる

Slide 26

Slide 26 text

再掲

Slide 27

Slide 27 text

old.cap = 5 Go Playground環境においてはintのet.sizeは4, sys.PtrSizeも4 newcap := old.cap doublecap := newcap + newcap ~略~ if old.len < 1024 { newcap = doublecap } else { ~略~ case et.size == sys.PtrSize: ~略~ capmem = roundupsize(uintptr(newcap) * sys.PtrSize) ~略~ newcap = int(capmem / sys.PtrSize)

Slide 28

Slide 28 text

計算してみたところ、ちゃんと結果が12になりまし た。 https://play.golang.org/p/oSn8GbSWVkK

Slide 29

Slide 29 text

まとめ スライスの容量拡張される際の新容量は、元容量 の大体2倍である。 (今回の話から省いたが、スラ イス長が大きい場合(1024が閾値)は大体1.25倍) きっちり2倍にならないのは、メモリ管理上キリの よいところまでメモリ確保量が切り上げされてい るからである。

Slide 30

Slide 30 text

No content