Slide 1

Slide 1 text

Хэш таблицы в Go. Детали Хэш таблицы в Go. Детали реализации реализации Колистратова Дарья Колистратова Дарья Software Engineer, RetailNext Software Engineer, RetailNext

Slide 2

Slide 2 text

Что такое хэш-таблица? Что такое хэш-таблица? Необходимые атрибуты: Необходимые атрибуты: Функция маппинга Функция маппинга map(key) → value map(key) → value Вставка Вставка insert(map, key, value) insert(map, key, value) Удаление Удаление delete(map, key) delete(map, key) Поиск Поиск lookup(key) → value lookup(key) → value 2 2

Slide 3

Slide 3 text

Хэш-таблица в языке go Хэш-таблица в языке go Создание Создание m := make(map[key_type]value_type) m := make(map[key_type]value_type) m := new(map[key_type]value_type) m := new(map[key_type]value_type) var m map[key_type]value_type var m map[key_type]value_type m := map[key_type]value_type{key1: val1, key2: val2} m := map[key_type]value_type{key1: val1, key2: val2} Вставка Вставка m[key] = value m[key] = value Удаление Удаление delete(m, key) delete(m, key) Поиск Поиск value = m[key] value = m[key] value, ok = m[key] value, ok = m[key] 3 3

Slide 4

Slide 4 text

Обход таблицы Обход таблицы Причина: Причина: // mapiterinit initializes the hiter struct used for ranging over maps. // mapiterinit initializes the hiter struct used for ranging over maps. func mapiterinit(t *maptype, h *hmap, it *hiter) {... func mapiterinit(t *maptype, h *hmap, it *hiter) {... // decide where to start // decide where to start r := uintptr(fastrand()) r := uintptr(fastrand()) ... ... it.startBucket = r & bucketMask(h.B)...} it.startBucket = r & bucketMask(h.B)...} package main package main import "fmt" import "fmt" func main() { func main() { m := map[int]bool{} m := map[int]bool{} for i := 0; i < 50; i++ { for i := 0; i < 50; i++ { m[i] = ((i % 2) == 0) m[i] = ((i % 2) == 0) } } for k, v := range m { for k, v := range m { fmt.Printf("key: %d, value: %t\n", k, v) fmt.Printf("key: %d, value: %t\n", k, v) } } } } Run 4 4

Slide 5

Slide 5 text

Поиск в таблице Поиск в таблице package main package main import ( import ( "fmt" "fmt" ) ) func main() { func main() { m := map[int]int{0: 0, 1: 10} m := map[int]int{0: 0, 1: 10} fmt.Println(m, m[0], m[1], m[2]) fmt.Println(m, m[0], m[1], m[2]) } } Run 5 5

Slide 6

Slide 6 text

Поиск в таблице Поиск в таблице "An attempt to fetch a map value with a key that is not present in the map will return the "An attempt to fetch a map value with a key that is not present in the map will return the zero value for the type of the entries in the map. zero value for the type of the entries in the map. Sometimes you need to distinguish a missing entry from a zero value. You can discriminate Sometimes you need to distinguish a missing entry from a zero value. You can discriminate with a form of multiple assignment." - спецификация Go. with a form of multiple assignment." - спецификация Go. package main package main import ( import ( "fmt" "fmt" ) ) func main() { func main() { m := map[int]int{0: 0, 1: 10} m := map[int]int{0: 0, 1: 10} m2, ok := m[2] m2, ok := m[2] if !ok { if !ok { // somehow process this case // somehow process this case m2 = 20 m2 = 20 } } fmt.Println(m, m[0], m[1], m2) fmt.Println(m, m[0], m[1], m2) } } Run 6 6

Slide 7

Slide 7 text

Создание таблицы Создание таблицы VS VS package main package main func main() { func main() { var m map[string]int var m map[string]int for _, word := range []string{"hello", "world", "from", "the", for _, word := range []string{"hello", "world", "from", "the", "best", "language", "in", "the", "world"} { "best", "language", "in", "the", "world"} { m[word]++ m[word]++ println(word, m[word]) println(word, m[word]) } } } } Run package main package main func main() { func main() { m := make(map[string]int) m := make(map[string]int) for _, word := range []string{"hello", "world", "from", "the", for _, word := range []string{"hello", "world", "from", "the", "best", "language", "in", "the", "world"} { "best", "language", "in", "the", "world"} { m[word]++ m[word]++ println(word, m[word]) println(word, m[word]) } } } } Run 7 7

Slide 8

Slide 8 text

Как передается map в функцию? Как передается map в функцию? VS VS package main package main func foo(n int) { n = 10 } func foo(n int) { n = 10 } func main() { func main() { n := 15 n := 15 println("n before foo =", n) println("n before foo =", n) foo(n) foo(n) println("n after foo =", n) println("n after foo =", n) } } Run package main package main func foo(m map[int]int) { m[10] = 10 } func foo(m map[int]int) { m[10] = 10 } func main() { func main() { m := make(map[int]int) m := make(map[int]int) m[10] = 15 m[10] = 15 println("m[10] before foo =", m[10]) println("m[10] before foo =", m[10]) foo(m) foo(m) println("m[10] after foo =", m[10]) println("m[10] after foo =", m[10]) } } Run 8 8

Slide 9

Slide 9 text

Map передается ссылке? Map передается ссылке? -- Нет. -- Нет. * в go не бывает ссылок. Зато есть указатели. * в go не бывает ссылок. Зато есть указатели. package main package main import "fmt" import "fmt" func fn(m map[int]int) { func fn(m map[int]int) { m = make(map[int]int) m = make(map[int]int) fmt.Println("m == nil in fn?:", m == nil) fmt.Println("m == nil in fn?:", m == nil) } } func main() { func main() { var m map[int]int var m map[int]int fn(m) fn(m) fmt.Println("m == nil in main?:", m == nil) fmt.Println("m == nil in main?:", m == nil) } } Run 9 9

Slide 10

Slide 10 text

Тип map - что же это? Тип map - что же это? Исходный код из пакета runtime: Исходный код из пакета runtime: // A header for a Go map. // A header for a Go map. type hmap struct { type hmap struct { // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go. // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go. // Make sure this stays in sync with the compiler's definition. // Make sure this stays in sync with the compiler's definition. count int // # live cells == size of map. Must be first (used by len() builtin) count int // # live cells == size of map. Must be first (used by len() builtin) flags uint8 flags uint8 B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items) B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items) noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details hash0 uint32 // hash seed hash0 uint32 // hash seed buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0. buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0. oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacu nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacu extra *mapextra // optional fields extra *mapextra // optional fields } } 10 10

Slide 11

Slide 11 text

Реализация Реализация 11 11

Slide 12

Slide 12 text

Как растет map? Как растет map? map source: // Maximum average load of a bucket that triggers growth is 6.5. map source: // Maximum average load of a bucket that triggers growth is 6.5. Если в каждом "ведре" более 6,5 элементов, происходит увеличение массива. Если в каждом "ведре" более 6,5 элементов, происходит увеличение массива. Выделяется в 2 раза больше массив. Выделяется в 2 раза больше массив. Старые данные копируются в него. Старые данные копируются в него. - это происходит не за один подход, а понемногу каждые вставку и удаление, - это происходит не за один подход, а понемногу каждые вставку и удаление, поэтому операции будут чуть медленнее в процессе эвакуации данных. поэтому операции будут чуть медленнее в процессе эвакуации данных. Начинают использоваться новые. Начинают использоваться новые. 12 12

Slide 13

Slide 13 text

Взятие адреса элемента map. Взятие адреса элемента map. package main package main import ( import ( "fmt" "fmt" ) ) func main() { func main() { m := make(map[int]int) m := make(map[int]int) m[1] = 10 m[1] = 10 a := &m[1] a := &m[1] fmt.Println(m[1], *a) fmt.Println(m[1], *a) } } Run 13 13

Slide 14

Slide 14 text

Как реализована map без genericов? Как реализована map без genericов? Используется interface{}? Используется interface{}? - Нет. - Нет. Может быть, кодогенераця? Может быть, кодогенераця? - Тоже нет. - Тоже нет. 14 14

Slide 15

Slide 15 text

Как реализована map без genericов? Как реализована map без genericов? Замена во время компиляции. Замена во время компиляции. v := m["k"] → func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer v := m["k"] → func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer v, ok := m["k"] → func mapaccess2(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, bool) v, ok := m["k"] → func mapaccess2(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, bool) m["k"] = 9001 → func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer m["k"] = 9001 → func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer delete(m, "k") → func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) delete(m, "k") → func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) Все операции используют unsafe.Pointers на значения общего типа. Все операции используют unsafe.Pointers на значения общего типа. Информация о типе каждого значения описывается дескриптором типа. Информация о типе каждого значения описывается дескриптором типа. Для дескриптора типа определены операции ==, hash, размеры и т.д. Для дескриптора типа определены операции ==, hash, размеры и т.д. type mapType struct { type mapType struct { key *_type key *_type elem *_type ...} elem *_type ...} type _type struct { type _type struct { size uintptr size uintptr alg *typeAlg ...} alg *typeAlg ...} type typeAlg struct { type typeAlg struct { hash func(unsafe.Pointer, uintptr) uintptr hash func(unsafe.Pointer, uintptr) uintptr equal func(unsafe.Pointer, unsafe.Pointer) bool } equal func(unsafe.Pointer, unsafe.Pointer) bool } 15 15

Slide 16

Slide 16 text

Пример Пример Поиск трансформируется во что-то подобное: Поиск трансформируется во что-то подобное: v = m[k] -> v = m[k] -> { { kPointer := unsafe.Pointer(&k) kPointer := unsafe.Pointer(&k) vPointer := mapaccess1(typeOf(m), m, kPointer) vPointer := mapaccess1(typeOf(m), m, kPointer) v = *(*typeOfvalue)vPointer v = *(*typeOfvalue)vPointer } } 16 16

Slide 17

Slide 17 text

Пример Пример // lookup looks up a key in a map and returns a pointer to the associated value. // lookup looks up a key in a map and returns a pointer to the associated value. // t = type of the map // t = type of the map // m = map // m = map // key = pointer to key // key = pointer to key func lookup(t *mapType, m *mapHeader, key unsafe.Pointer) unsafe.Pointer { func lookup(t *mapType, m *mapHeader, key unsafe.Pointer) unsafe.Pointer { if m == nil || m.count == 0 { if m == nil || m.count == 0 { return zero return zero } } hash := t.key.hash(key, m.seed) // hash := hashfn(key) hash := t.key.hash(key, m.seed) // hash := hashfn(key) bucket := hash & (1<> 56) // extra := top 8 bits of hash extra := byte(hash >> 56) // extra := top 8 bits of hash b := (*bucket)(add(m.buckets, bucket*t.bucketsize)) // b := &m.buckets[bucket] b := (*bucket)(add(m.buckets, bucket*t.bucketsize)) // b := &m.buckets[bucket] - - by Keith Randall by Keith Randall 17 17

Slide 18

Slide 18 text

Пример Пример for { for { for i := 0; i < 8; i++ { for i := 0; i < 8; i++ { if b.extra[i] != extra { // check 8 extra hash bits if b.extra[i] != extra { // check 8 extra hash bits continue continue } } k := add(b, dataOffset+i*t.key.size) // pointer to ki in bucket k := add(b, dataOffset+i*t.key.size) // pointer to ki in bucket if t.key.equal(key, k) { if t.key.equal(key, k) { // return pointer to vi // return pointer to vi return add(b, dataOffset+8*t.key.size+i*t.value.size) return add(b, dataOffset+8*t.key.size+i*t.value.size) } } } } b = b.overflow b = b.overflow if b == nil { if b == nil { return zero return zero } } } } - - by Keith Randall by Keith Randall 18 18

Slide 19

Slide 19 text

Массивы Массивы Фиксированный размер. Фиксированный размер. Является Является value type value type. . *почитать про value type и reference type в го: *почитать про value type и reference type в го: About the terminology "reference type" in Go About the terminology "reference type" in Go (https://github.com/go101/go101/wiki/About-the-terminology-%22reference-type%22-in-Go) (https://github.com/go101/go101/wiki/About-the-terminology-%22reference-type%22-in-Go) package main package main import "fmt" import "fmt" func main() { func main() { var a [5]int var a [5]int b := a b := a b[2] = 7 b[2] = 7 fmt.Println(a) fmt.Println(a) fmt.Println(b) fmt.Println(b) } } Run 19 19

Slide 20

Slide 20 text

Слайсы Слайсы Не фиксированный размер. Не фиксированный размер. Ведет себя как Ведет себя как reference type reference type. . Содержит в себе указатель на массив. Содержит в себе указатель на массив. 2 слайса могут ссылаться на один и тот же массив. 2 слайса могут ссылаться на один и тот же массив. package main package main import "fmt" import "fmt" func main() { func main() { a := make([]int, 5) a := make([]int, 5) b := a b := a b[2] = 7 b[2] = 7 fmt.Println(a) fmt.Println(a) fmt.Println(b) fmt.Println(b) } } Run 20 20

Slide 21

Slide 21 text

Внутреннее устройство Внутреннее устройство type slice struct { type slice struct { array unsafe.Pointer array unsafe.Pointer len int len int cap int cap int } } Важно: Важно: слайс слайс копируется копируется когда передается в функцию: когда передается в функцию: package main package main import "fmt" import "fmt" func makeBigger(s []int) { func makeBigger(s []int) { s = append(s, s...) s = append(s, s...) fmt.Println("Inside: ", s, len(s)) fmt.Println("Inside: ", s, len(s)) } } func main() { func main() { s := []int{1, 2, 3} s := []int{1, 2, 3} makeBigger(s) makeBigger(s) fmt.Println("After: ", s, len(s)) fmt.Println("After: ", s, len(s)) } } Run 21 21

Slide 22

Slide 22 text

Слайсы VS мапы Слайсы VS мапы Посмотрим на бенчмарки: Посмотрим на бенчмарки: const structSize = 30 const structSize = 30 func lookupMapStr(m map[string]bool, key string) bool { func lookupMapStr(m map[string]bool, key string) bool { return m[key] return m[key] } } func lookupSliceStr(s []string, key string) bool { func lookupSliceStr(s []string, key string) bool { for i := range s { for i := range s { if key == s[i] { if key == s[i] { return true return true } } } } return false return false } } 22 22

Slide 23

Slide 23 text

Слайсы VS мапы Слайсы VS мапы func initMapStr() map[string]bool { func initMapStr() map[string]bool { m := make(map[string]bool) m := make(map[string]bool) for i := 0; i < structSize; i++ { for i := 0; i < structSize; i++ { m[fmt.Sprintf("%d", i)] = true m[fmt.Sprintf("%d", i)] = true } } return m return m } } func initSliceStr() []string { func initSliceStr() []string { var s []string var s []string for i := 0; i < structSize; i++ { for i := 0; i < structSize; i++ { s = append(s, fmt.Sprintf("%d", i)) s = append(s, fmt.Sprintf("%d", i)) } } return s return s } } 23 23

Slide 24

Slide 24 text

Слайсы VS мапы Слайсы VS мапы type Bench struct { type Bench struct { Name, Element, Type, Struct string Name, Element, Type, Struct string IsStr bool IsStr bool } } type Data struct { type Data struct { Benches []Bench Benches []Bench } } var strBench = `{{with .Benches}} var strBench = `{{with .Benches}} {{range .}} {{range .}} func Benchmark{{.Struct}}{{.Type}}{{.Name}}(b *testing.B) { func Benchmark{{.Struct}}{{.Type}}{{.Name}}(b *testing.B) { t := init{{.Struct}}{{.Type}}() t := init{{.Struct}}{{.Type}}() el := {{if .IsStr}}fmt.Sprintf("%d", {{end}}{{.Element}}{{if .IsStr}}){{end}} el := {{if .IsStr}}fmt.Sprintf("%d", {{end}}{{.Element}}{{if .IsStr}}){{end}} b.ResetTimer() b.ResetTimer() for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ { lookup{{.Struct}}{{.Type}}(t, el) lookup{{.Struct}}{{.Type}}(t, el) } } } } {{end}} {{end}} {{end}} {{end}} ` ` 24 24

Slide 25

Slide 25 text

Слайсы VS мапы Слайсы VS мапы tmpl := template.Must(template.New("Bench").Parse(strBench)) tmpl := template.Must(template.New("Bench").Parse(strBench)) tmpl.Execute(f, Data{Benches: b}) tmpl.Execute(f, Data{Benches: b}) func BenchmarkMapIntNotExist(b *testing.B) { func BenchmarkMapIntNotExist(b *testing.B) { t := initMapInt() t := initMapInt() el := -1 el := -1 b.ResetTimer() b.ResetTimer() for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ { lookupMapInt(t, el) lookupMapInt(t, el) } } } } func BenchmarkMapStrNotExist(b *testing.B) { func BenchmarkMapStrNotExist(b *testing.B) { t := initMapStr() t := initMapStr() el := fmt.Sprintf("%d", -1) el := fmt.Sprintf("%d", -1) b.ResetTimer() b.ResetTimer() for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ { lookupMapStr(t, el) lookupMapStr(t, el) } } } } 25 25

Slide 26

Slide 26 text

Слайсы VS мапы Слайсы VS мапы 26 26

Slide 27

Slide 27 text

Слайсы VS мапы Слайсы VS мапы 27 27

Slide 28

Slide 28 text

Заключение Заключение Используйте мапы! Используйте мапы! И знайте как и почему они работают. И знайте как и почему они работают. 28 28

Slide 29

Slide 29 text

Вопросы? Вопросы? 29 29

Slide 30

Slide 30 text

Ссылки Ссылки "Go maps in action", Andrew Gerrand "Go maps in action", Andrew Gerrand (https://blog.golang.org/go-maps-in-action) (https://blog.golang.org/go-maps-in-action) "How the go runtime implements maps efficiently", Dave Cheney "How the go runtime implements maps efficiently", Dave Cheney (https://dave.cheney.net/2018/05/29/how-the-go- (https://dave.cheney.net/2018/05/29/how-the-go- runtime-implements-maps-efficiently-without-generics#easy-footnote-1-3224) runtime-implements-maps-efficiently-without-generics#easy-footnote-1-3224) "Understanding type in go, William Kennedy "Understanding type in go, William Kennedy (https://www.ardanlabs.com/blog/2013/07/understanding-type-in-go.html) (https://www.ardanlabs.com/blog/2013/07/understanding-type-in-go.html) "Inside the Map Implementation", Keith Randall "Inside the Map Implementation", Keith Randall (https://www.youtube.com/watch?v=Tl7mi9QmLns&feature=youtu.be) (https://www.youtube.com/watch?v=Tl7mi9QmLns&feature=youtu.be) "map source code", Go Runtime "map source code", Go Runtime (https://github.com/golang/go/blob/master/src/runtime/map.go) (https://github.com/golang/go/blob/master/src/runtime/map.go) golang spec golang spec (https://golang.org/ref/spec) (https://golang.org/ref/spec) effective go effective go (https://golang.org/doc/effective_go.html) (https://golang.org/doc/effective_go.html) картинки с гоферами картинки с гоферами (https://github.com/shalakhin/gophericons) (https://github.com/shalakhin/gophericons) 30 30

Slide 31

Slide 31 text

Thank you Thank you Колистратова Дарья Колистратова Дарья Software Engineer, RetailNext Software Engineer, RetailNext daria.kolistratova@gmail.com daria.kolistratova@gmail.com (mailto:daria.kolistratova@gmail.com) (mailto:daria.kolistratova@gmail.com) https://www.facebook.com/daria.kolistratova https://www.facebook.com/daria.kolistratova (https://www.facebook.com/daria.kolistratova) (https://www.facebook.com/daria.kolistratova) https://vk.com/dakolistratova https://vk.com/dakolistratova (https://vk.com/dakolistratova) (https://vk.com/dakolistratova)

Slide 32

Slide 32 text

No content