Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Страшные встроенные структуры

Страшные встроенные структуры

Доклад, в котором я разбираю структуры list и dict: как они устроеные внутри и как разложены в памяти.

Denis Anikin

May 23, 2022
Tweet

More Decks by Denis Anikin

Other Decks in Programming

Transcript

  1. Немного о памяти 3 — Память это массив байтов с

    точки зрения софта (byte-addressed). Для нас это бесконечный поток адресов конкретных байтов — Оперативная память хорошо параллелизирует операции
  2. Адреса в оперативной памяти 4 — Вы можете по ним

    перемещаться с помощью вычитания и сложения — По сути мы с вами оперируем несколькими большими сущностями: записями и массивами
  3. Кое-что еще 12 Каждый раз, когда вы хотите обратиться к

    элементам массивов, вы вынуждены собирать питон объекты. Если вы захотите сделать sum массива из 100 float, вы получите сборку >100 объектов
  4. Важные дополнения 15 — При копировании мы всего-лишь переносим ссылки

    — Чтобы достать item, получить len, нам достаточно вытащить 8 байт!
  5. Реалокация 😬 19 >>> list(range(10)) [0, 1, 2, 3, 4,

    5, 6, 7, 8, 9] >>> sum(range(10)) 45 >>> sum(range(1000)) 499500 >>> sum(range(1_000_000)) 499999500000
  6. Как же лист борется с append’ами? 20 — При аппенде

    в наполненный список добавляется не 1 слот, с каждым разом все больше: 4, 8, 16, 25, 35, 46, 58, 72, 88, … — Это называется амортизацией
  7. Есть и цена 22 Если у вы добавляете 991 первый

    элемент к списку из 990 элементов, то получаете аллокацию 1120 «слотов» и копирование 990 элементов «за занавеской»
  8. Не все так плохо: list использует 94%* места и требует

    около 6% экстра-места для будущих append *в среднем **кажется… 23
  9. List — опасен! 24 s = [1, 2, 3, …]

    — s.insert(0, v) — s.pop(0)
  10. List — опасен! 27 s = [1, 2, 3, …]

    — 1000 элементов — Делаем s[400:500] — Получаем 800 байт копирования и новый список в 100 элементов!
  11. Как нам превратить ключ в индекс? 31 У нас есть

    что угодно: — строка — число — tuple — и т.п.
  12. Как нам превратить ключ в индекс? 32 У нас есть

    что угодно: — строка — число — tuple — и т.п. Из этого должен получиться индекс!
  13. Давайте поговорим о хеше 35 Что это вообще — Функция,

    возвращающая предсказуемое число на любые входные данные
  14. Давайте поговорим о хеше 36 Что это вообще — Функция,

    возвращающая предсказуемое число на любые входные данные — На 32битной платформе возвращет 32 бита, на 64 — 64
  15. Давайте поговорим о хеше 37 Что это вообще — Функция,

    возвращающая предсказуемое число на любые входные данные — На 32битной платформе возвращет 32 бита, на 64 — 64 — У каждого типа свое хеширование.
  16. Как работает dict 40 Представим my_dict = {} bucket offset

    (key, value) pairs references 000 001 010 011 100 101 110 111
  17. Как работает dict 41 Представим my_dict = {} Мы хотим

    сделать следующее: bucket offset (key, value) pairs references 000 001 010 011 100 101 110 111 my_dict['newkey'] = 1 my_dict['privet'] = 2
  18. Как работает dict 42 Представим my_dict = {} bucket offset

    (key, value) pairs references 000 001 010 011 100 101 110 111 >>> hash('newkey') 1521937699834405394
  19. Как работает dict 43 Представим my_dict = {} bucket offset

    (key, value) pairs references 000 001 010 011 100 101 110 111 >>> hash('newkey') 1521937699834405394 >>> bin(hash('newkey')) 1010100011111000000100100011001000000111001111010001000010010
  20. Как работает dict 44 Представим my_dict = {} bucket offset

    (key, value) pairs references 000 001 010 011 100 101 110 111 >>> hash('newkey') 1521937699834405394 >>> bin(hash('newkey')) 1010100011111000000100100011001000000111001111010001000010010
  21. Как работает dict 45 Представим my_dict = {} bucket offset

    (key, value) pairs references 000 001 010 (‘newkey’, 1) 011 100 101 110 111 >>> hash('newkey') 1521937699834405394 >>> bin(hash('newkey')) 1010100011111000000100100011001000000111001111010001000010010
  22. Как работает dict 46 Представим my_dict = {} bucket offset

    (key, value) pairs references 000 001 010 (‘newkey’, 1) 011 (‘privet’, 2) 100 101 110 111 >>> hash('newkey') 1521937699834405394 >>> bin(hash('newkey')) 1010100011111000000100100011001000000111001111010001000010010 >>> bin(hash('privet')) 101000100111100111100111001010111111110100111000010110011
  23. Как работает dict, когда значений больше 8? J 47 —

    При загрузке более 2/3 словаря, происходит увеличение массива вдвое — Средняя загрузка колеблется между 1/3 и 2/3
  24. Open addressing 54 — Эффективнее по памяти (как ни странно)

    — Берет пробы приблизительно так: — Linear probing имеет кучу проблем (primary clustering, например). Поэтому существуют quadtratic probing и пр. — Все это крайне чувствительно к хеш функции (нужно равномерное распределение) probes[i] = hash(key) + i % number_of_buckets
  25. Compact dict (>=3.6) 56 # было hash_table = [ ('--',

    '--', '--'), (542403711206072985, 'two', 2), ('--', '--', '--'), (4677866115915370763, 'three', 3), ('--', '--', '--'), (-1182584047114089363, 'one', 1), ('--', '--', '--'), ('--', '--', '--') ] # стало hash_table = [None, 1, None, 2, None, 0, None, None] entries = [ (-1182584047114089363, 'one', 1), (542403711206072985, 'two', 2), (4677866115915370763, 'three', 3), ]