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

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

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

Iskander (Alex) Sharipov

February 16, 2019
Tweet

More Decks by Iskander (Alex) Sharipov

Other Decks in Programming

Transcript

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

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

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

    Хэш-таблица в языке go Создание Создание Создание m := make(map[key_type]value_type) 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) m := new(map[key_type]value_type) var m 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 := map[key_type]value_type{key1: val1, key2: val2} Вставка Вставка Вставка m[key] = value m[key] = value m[key] = value Удаление Удаление Удаление delete(m, key) delete(m, key) delete(m, key) Поиск Поиск Поиск value = m[key] value = m[key] value = m[key] value, ok = m[key] value, ok = m[key] value, ok = m[key] 3 3 3
  4. Обход Обход таблицы таблицы Обход таблицы Причина Причина: : Причина:

    // mapiterinit initializes the hiter struct used for ranging over maps. // 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) {... func mapiterinit(t *maptype, h *hmap, it *hiter) {... // decide where to start // decide where to start // decide where to start r := uintptr(fastrand()) r := uintptr(fastrand()) r := uintptr(fastrand()) ... ... ... it.startBucket = r & bucketMask(h.B)...} it.startBucket = r & bucketMask(h.B)...} it.startBucket = r & bucketMask(h.B)...} package main package main package main import "fmt" import "fmt" import "fmt" func main() { func main() { func main() { m := map[int]bool{} m := map[int]bool{} m := map[int]bool{} for i := 0; i < 50; i++ { for i := 0; i < 50; i++ { for i := 0; i < 50; i++ { m[i] = ((i % 2) == 0) m[i] = ((i % 2) == 0) m[i] = ((i % 2) == 0) } } } for k, v := range m { 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) fmt.Printf("key: %d, value: %t\n", k, v) } } } } } } Run 4 4 4
  5. Поиск Поиск в в таблице таблице Поиск в таблице package

    main package main package main import ( import ( import ( "fmt" "fmt" "fmt" ) ) ) func main() { func main() { func main() { m := map[int]int{0: 0, 1: 10} 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]) fmt.Println(m, m[0], m[1], m[2]) } } } Run 5 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 "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. 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 Sometimes you need to distinguish a missing entry from a zero value. You can discriminate with a form of multiple assignment." - with a form of multiple assignment." - спецификация спецификация Go. Go. with a form of multiple assignment." - спецификация Go. package main package main package main import ( import ( import ( "fmt" "fmt" "fmt" ) ) ) func main() { func main() { func main() { m := map[int]int{0: 0, 1: 10} m := map[int]int{0: 0, 1: 10} m := map[int]int{0: 0, 1: 10} m2, ok := m[2] m2, ok := m[2] m2, ok := m[2] if !ok { if !ok { if !ok { // somehow process this case // somehow process this case // somehow process this case m2 = 20 m2 = 20 m2 = 20 } } } fmt.Println(m, m[0], m[1], m2) fmt.Println(m, m[0], m[1], m2) fmt.Println(m, m[0], m[1], m2) } } } Run 6 6 6
  7. Создание Создание таблицы таблицы Создание таблицы VS VS VS package

    main package main package main func main() { func main() { func main() { var m map[string]int 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", for _, word := range []string{"hello", "world", "from", "the", "best", "language", "in", "the", "world"} { "best", "language", "in", "the", "world"} { "best", "language", "in", "the", "world"} { m[word]++ m[word]++ m[word]++ println(word, m[word]) println(word, m[word]) println(word, m[word]) } } } } } } Run package main package main package main func main() { func main() { func main() { m := make(map[string]int) 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", for _, word := range []string{"hello", "world", "from", "the", "best", "language", "in", "the", "world"} { "best", "language", "in", "the", "world"} { "best", "language", "in", "the", "world"} { m[word]++ m[word]++ m[word]++ println(word, m[word]) println(word, m[word]) println(word, m[word]) } } } } } } Run 7 7 7
  8. Как Как передается передается map map в в функцию функцию?

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

    -- -- Нет Нет. . -- Нет. * * в в go go не не бывает бывает ссылок ссылок. . Невозможно Невозможно создать создать 2 2 переменные переменные с с одним одним адресом адресом, , * в go не бывает ссылок. Невозможно создать 2 переменные с одним адресом, зато зато можно можно создать создать 2 2 переменные переменные, , указывающие указывающие на на один один адрес адрес ( (но но это это уже уже не не ссылка ссылка, , а а указатель указатель) ) зато можно создать 2 переменные, указывающие на один адрес (но это уже не ссылка, а указатель) package main package main package main import "fmt" import "fmt" import "fmt" func fn(m map[int]int) { func fn(m map[int]int) { func fn(m map[int]int) { m = make(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) fmt.Println("m == nil in fn?:", m == nil) } } } func main() { func main() { func main() { var m map[int]int var m map[int]int var m map[int]int fn(m) fn(m) fn(m) fmt.Println("m == nil in main?:", m == nil) fmt.Println("m == nil in main?:", m == nil) fmt.Println("m == nil in main?:", m == nil) } } } Run 9 9 9
  10. Тип Тип map - map - что что же же

    это это? ? Тип map - что же это? Исходный Исходный код код из из пакета пакета runtime: runtime: Исходный код из пакета runtime: // A header for a Go map. // A header for a Go map. // A header for a Go map. type hmap struct { 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. // 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. // 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) count int // # live cells == size of map. Must be first (used by len() builtin) flags uint8 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) 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 noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details hash0 uint32 // hash seed 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. 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 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 nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacu extra *mapextra // optional fields extra *mapextra // optional fields extra *mapextra // optional fields } } } 10 10 10
  11. Как Как растет растет map? 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. map source: // Maximum average load of a bucket that triggers growth is 6.5. Если Если в в каждом каждом " "ведре ведре" " более более 6,5 6,5 элементов элементов, , происходит происходит увеличение увеличение массива массива. . Если в каждом "ведре" более 6,5 элементов, происходит увеличение массива. Выделяется Выделяется в в 2 2 раза раза больше больше массив массив. . Выделяется в 2 раза больше массив. Старые Старые данные данные копируются копируются в в него него. . Старые данные копируются в него. - - это это происходит происходит не не за за один один подход подход, , а а понемногу понемногу каждые каждые вставку вставку и и удаление удаление, , - это происходит не за один подход, а понемногу каждые вставку и удаление, поэтому поэтому операции операции будут будут чуть чуть медленнее медленнее в в процессе процессе эвакуации эвакуации данных данных. . поэтому операции будут чуть медленнее в процессе эвакуации данных. Начинают Начинают использоваться использоваться новые новые. . Начинают использоваться новые. 12 12 12
  12. Взятие Взятие адреса адреса элемента элемента map. map. Взятие адреса

    элемента map. package main package main package main import ( import ( import ( "fmt" "fmt" "fmt" ) ) ) func main() { func main() { func main() { m := make(map[int]int) m := make(map[int]int) m := make(map[int]int) m[1] = 10 m[1] = 10 m[1] = 10 a := &m[1] a := &m[1] a := &m[1] fmt.Println(m[1], *a) fmt.Println(m[1], *a) fmt.Println(m[1], *a) } } } Run 13 13 13
  13. Как Как реализована реализована map map без без generic genericов

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

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

    что- -то то подобное подобное: : Поиск трансформируется во что-то подобное: v = m[k] -> v = m[k] -> v = m[k] -> { { { kPointer := unsafe.Pointer(&k) kPointer := unsafe.Pointer(&k) kPointer := unsafe.Pointer(&k) vPointer := mapaccess1(typeOf(m), m, kPointer) vPointer := mapaccess1(typeOf(m), m, kPointer) vPointer := mapaccess1(typeOf(m), m, kPointer) v = *(*typeOfvalue)vPointer v = *(*typeOfvalue)vPointer v = *(*typeOfvalue)vPointer } } } 16 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. // 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 // t = type of the map // m = map // m = map // m = map // key = pointer to key // 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 { func lookup(t *mapType, m *mapHeader, key unsafe.Pointer) unsafe.Pointer { if m == nil || m.count == 0 { if m == nil || m.count == 0 { if m == nil || m.count == 0 { return zero return zero return zero } } } hash := t.key.hash(key, m.seed) // hash := hashfn(key) 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 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 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] b := (*bucket)(add(m.buckets, bucket*t.bucketsize)) // b := &m.buckets[bucket] - - - by Keith Randall by Keith Randall by Keith Randall 17 17 17
  17. Пример Пример Пример for { for { for { for

    i := 0; i < 8; i++ { 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 if b.extra[i] != extra { // check 8 extra hash bits continue 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 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) { if t.key.equal(key, k) { // return pointer to vi // 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) return add(b, dataOffset+8*t.key.size+i*t.value.size) } } } } } } b = b.overflow b = b.overflow b = b.overflow if b == nil { if b == nil { if b == nil { return zero return zero return zero } } } } } } - - - by Keith Randall by Keith Randall by Keith Randall 18 18 18
  18. Заключение Заключение Заключение Используйте Используйте мапы мапы! ! Используйте мапы!

    И И знайте знайте как как и и почему почему они они работают работают. . И знайте как и почему они работают. 19 19 19
  19. Ссылки Ссылки Ссылки "Go maps in action", Andrew Gerrand "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) (https://blog.golang.org/go-maps-in-action) "How the go runtime implements maps e ciently", Dave Cheney "How the go runtime implements maps e ciently", Dave Cheney "How the go runtime implements maps e ciently", Dave Cheney (https://dave.cheney.net/2018/05/29/how-the-go- (https://dave.cheney.net/2018/05/29/how-the-go- (https://dave.cheney.net/2018/05/29/how-the-go- runtime-implements-maps-e ciently-without-generics#easy-footnote-1-3224) runtime-implements-maps-e ciently-without-generics#easy-footnote-1-3224) runtime-implements-maps-e ciently-without-generics#easy-footnote-1-3224) "Understanding type in go, William Kennedy "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) (https://www.ardanlabs.com/blog/2013/07/understanding-type-in-go.html) "Inside the Map Implementation", Keith Randall "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) (https://www.youtube.com/watch?v=Tl7mi9QmLns&feature=youtu.be) "map source code", Go Runtime "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) (https://github.com/golang/go/blob/master/src/runtime/map.go) golang spec golang spec golang spec (https://golang.org/ref/spec) (https://golang.org/ref/spec) (https://golang.org/ref/spec) e ective go e ective go e ective go (https://golang.org/doc/e ective_go.html) (https://golang.org/doc/e ective_go.html) (https://golang.org/doc/e ective_go.html) картинки картинки с с гоферами гоферами картинки с гоферами (https://github.com/shalakhin/gophericons) (https://github.com/shalakhin/gophericons) (https://github.com/shalakhin/gophericons) 21 21 21
  20. Thank you Thank you Thank you Колистратова Колистратова Дарья Дарья

    Колистратова Дарья Software Engineer, RetailNext Software Engineer, RetailNext Software Engineer, RetailNext [email protected] [email protected] [email protected] (mailto:[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://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) (https://vk.com/dakolistratova) (https://vk.com/dakolistratova)