Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Хэш таблицы в Go: детали реализации

Хэш таблицы в Go: детали реализации

Iskander (Alex) Sharipov

July 27, 2019
Tweet

More Decks by Iskander (Alex) Sharipov

Other Decks in Programming

Transcript

  1. Хэш таблицы в Go. Детали Хэш таблицы в Go. Детали

    реализации реализации Колистратова Дарья Колистратова Дарья Software Engineer, RetailNext Software Engineer, RetailNext
  2. Что такое хэш-таблица? Что такое хэш-таблица? Необходимые атрибуты: Необходимые атрибуты:

    Функция маппинга Функция маппинга 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
  3. Хэш-таблица в языке 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
  4. Обход таблицы Обход таблицы Причина: Причина: // 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
  5. Поиск в таблице Поиск в таблице 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
  6. Поиск в таблице Поиск в таблице "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
  7. Создание таблицы Создание таблицы 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
  8. Как передается 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
  9. 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
  10. Тип 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
  11. Как растет 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
  12. Взятие адреса элемента 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
  13. Как реализована map без genericов? Как реализована map без genericов?

    Используется interface{}? Используется interface{}? - Нет. - Нет. Может быть, кодогенераця? Может быть, кодогенераця? - Тоже нет. - Тоже нет. 14 14
  14. Как реализована 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
  15. Пример Пример Поиск трансформируется во что-то подобное: Поиск трансформируется во

    что-то подобное: 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
  16. Пример Пример // 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<<m.logB-1) // bucket := hash % nbuckets bucket := hash & (1<<m.logB-1) // bucket := hash % nbuckets extra := byte(hash >> 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
  17. Пример Пример 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
  18. Массивы Массивы Фиксированный размер. Фиксированный размер. Является Является 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
  19. Слайсы Слайсы Не фиксированный размер. Не фиксированный размер. Ведет себя

    как Ведет себя как 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
  20. Внутреннее устройство Внутреннее устройство 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
  21. Слайсы 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
  22. Слайсы 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
  23. Слайсы 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
  24. Слайсы 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
  25. Заключение Заключение Используйте мапы! Используйте мапы! И знайте как и

    почему они работают. И знайте как и почему они работают. 28 28
  26. Ссылки Ссылки "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
  27. Thank you Thank you Колистратова Дарья Колистратова Дарья Software Engineer,

    RetailNext Software Engineer, RetailNext [email protected] [email protected] (mailto:[email protected]) (mailto:[email protected]) 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)