skiplistmap の最適化その1でsyn.Map にほぼread でおいついたけど、まだもう少し ということで次は bucket の構造をすこしいじって最適化をさらにすすめてみた
bucketBuffer というのを作成してlinked list でレベルごとに管理してたのだけど level 1 -> level2
type bucketBuffer struct {
start *list_head.ListHead
last *list_head.ListHead
buf []bucket
max int
list_head.ListHead
}
考えなしで設計したからか、レベルが深ければ bucketBuffer.buf のサイズがでかくなる ( 16 -> 256 -> 4096 -> 65536) level4 ではもうメモリを食いすぎるので パフォーマンス的には 3 ぐらいにしないと出ない状態だった
単一のバッファにしたりいろいろ考えたけど slice のappend があると当然realloc があるからアドレスが変わるので bucket のlinked list がつながらなくなる。
もっと単純にbucketBuffer を廃止して、bucket 自体を拡張することにした
len int64
start *list_head.ListHead // to MapEntry
- downLevel *list_head.ListHead // to bucket.Levelhead
+ downLevels []bucket
LevelHead list_head.ListHead // to same level bucket
list_head.ListHead
下位バケットは常に 上限が16個だし必要なものしかはえない。 不要なときはからっぽにして必要になったら len が1 でcapacity 16 で作成して
if cap(b.downLevels) == 0 {
b.downLevels = make([]bucket, 1, 16)
bucket が不要になったら bucket.downLevels のbucket のlinked list をはずしてprev/next を全部自己参照にして b.downLevels[0] 以外が自己参照になったら消すみたいな作りにした。こすると 0x321 というkey で検索したい場合は
map.buckets[1].downlevels[2].downlevels[3]
みたいな感じでbucket 検索はできる。
多次元slice をつかって一撃で
map.buckets[1][2][3]
とかでもいいんだろうが具体的にはみてないけど多次元slice の場合はかならず、要素がSliceHeader
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
になってしまうので具合がよくなさそう。 もっとbit 演算でばしっとなりそうだが、それは更に先でやればいいかなと。
だいぶ linked-list なのか?感はあるけどなんだかん削除追加の利便性としてはslice とlinked-list の いいところどりはしたい concurrent に使うにも ということで更にベンチを取りました。
Benchmark_Map/mapWithMutex__________________w/_0_bucket=__0-16 17055374 69.39 ns/op 15 B/op 1 allocs/op
Benchmark_Map/sync.Map______________________w/_0_bucket=__0-16 25268019 42.00 ns/op 63 B/op 2 allocs/op
Benchmark_Map/skiplistmap___________________w/_0_bucket=_32-16 26189863 47.96 ns/op 15 B/op 1 allocs/op
Benchmark_Map/skiplistmap___________________w/_0_bucket=_16-16 32570624 44.40 ns/op 15 B/op 1 allocs/op
Benchmark_Map/skiplistmap3__________________w/_0_bucket=_16-16 36449119 40.41 ns/op 15 B/op 1 allocs/op
Benchmark_Map/RMap__________________________w/_0_bucket=__0-16 34806978 33.39 ns/op 31 B/op 2 allocs/op
Benchmark_Map/mapWithMutex__________________w/50_bucket=__0-16 3314656 364.6 ns/op 16 B/op 1 allocs/op
Benchmark_Map/sync.Map______________________w/50_bucket=__0-16 14289441 70.34 ns/op 134 B/op 4 allocs/op
Benchmark_Map/skiplistmap___________________w/50_bucket=_32-16 21370478 64.18 ns/op 27 B/op 2 allocs/op
Benchmark_Map/skiplistmap___________________w/50_bucket=_16-16 21110790 65.22 ns/op 27 B/op 2 allocs/op
Benchmark_Map/RMap__________________________w/50_bucket=__0-16 21455697 53.36 ns/op 72 B/op 4 allocs/op
!
!!!
!!!!!
Benchmark_Map/sync.Map______________________w/_0_bucket=__0-16 42.00 ns/op
Benchmark_Map/skiplistmap3__________________w/_0_bucket=_16-16 40.41 ns/op
read でsync.Map を越えました!
うぉおおおおおおおおおおおおおおおおおおおおおお!!!!!!!!
いまま iMac Pro で実装作業しててそこでみてたのですが。
ついでに新たに購入したAMD Ryzen 7 5700G
な ubutun 環境だと
Benchmark_Map/mapWithMutex__________________w/_0_bucket=__0-16 41431610 28.34 ns/op 15 B/op 1 allocs/op
Benchmark_Map/sync.Map______________________w/_0_bucket=__0-16 50776604 23.00 ns/op 63 B/op 2 allocs/op
Benchmark_Map/skiplistmap___________________w/_0_bucket=_16-16 36047734 29.84 ns/op 15 B/op 1 allocs/op
Benchmark_Map/skiplistmap3__________________w/_0_bucket=_16-16 50611837 25.67 ns/op 15 B/op 1 allocs/op
Benchmark_Map/skiplistmap3__________________w/_0_bucket=_32-16 48226480 25.57 ns/op 15 B/op 1 allocs/op
Benchmark_Map/RMap__________________________w/_0_bucket=__0-16 55773645 20.60 ns/op 31 B/op 2 allocs/op
Benchmark_Map/mapWithMutex__________________w/50_bucket=__0-16 5165390 469.4 ns/op 16 B/op 2 allocs/op
Benchmark_Map/sync.Map______________________w/50_bucket=__0-16 29077965 40.88 ns/op 136 B/op 4 allocs/op
Benchmark_Map/skiplistmap___________________w/50_bucket=_16-16 27622237 43.30 ns/op 29 B/op 2 allocs/op
Benchmark_Map/skiplistmap3__________________w/50_bucket=_16-16 35663386 37.63 ns/op 27 B/op 2 allocs/op
Benchmark_Map/skiplistmap3__________________w/50_bucket=_32-16 31481182 37.29 ns/op 27 B/op 2 allocs/op
Benchmark_Map/RMap__________________________w/50_bucket=__0-16 37502930 31.29 ns/op 70 B/op 4 allocs/op
めっちゃはええな全然ちがうな! っていうのは置いておいて
Benchmark_Map/sync.Map______________________w/_0_bucket=__0-16 50776604 23.00 ns/op
Benchmark_Map/skiplistmap3__________________w/_0_bucket=_32-16 48226480 25.57 ns/op
あぁ… まだ負けとるやん….
ぐぐぐこの環境だとまだ負けてるんじゃが…
malloc の特性の違いとかかかな…
くやしいのでまだ続く
手がつきてきたので
- unsafe.Pointer 祭り
- item (key/value) の hash値の局所性を高めたallocation とかかなぁ(具体的にはbucket に持たせるとか?)具体的にtraverse するときはアドレス的に近いところを見に行くようにする。再配置とかもいりそう
というめんどくせえなぁ。たいへんだなぁ
みたいなのしか残ってない…