New dict implementation
in Python 3.6
Inada Naoki (@methane)
Slide 2
Slide 2 text
自己紹介
@methane
K-Labo, KLab Inc.
Python core developer
C, Go, Network (server) programming, MySQL clients
ISUCON 6 winner (See http://isucon.net/ )
Slide 3
Slide 3 text
Table of contents
● dict in Python
● Python 3.5 implementation
● Python 3.6 implementation
● Toward Python 3.7
Slide 4
Slide 4 text
Dict in Python
Slide 5
Slide 5 text
Dict
Key-Value storage. A.k.a. associative-array, map, hash.
x = {"foo": 42, "bar": 84}
print( x["foo"] ) # => 42
Key feature:
● Constant time lookup
● Amortized constant time insertion
● Support custom (user-defined) key type
Slide 6
Slide 6 text
Dicts are everywhere in Python
x = 5 # global namespace is dict. Insert 'x' to it.
def add(a): # Insert 'add' to global dict
return a + x # lookup 'x' from global dict
print(add(7)) # search 'print' and 'add' from global dict
There are many dicts in Python program.
Lookup speed is critical.
Insertion speed and memory usage is very important too.
Slide 7
Slide 7 text
Python 3.5 implementation
Slide 8
Slide 8 text
Key
hash
value
0 1 2 3 4 5 6 7
d["foo"] = "spam" # insert new item
hash("foo") = 42 # hash value is 42
42 % 8 = 2 # hash value % hash table size = 2
Key
hash
value
0 1 2 3 4 5 6 7
"Open addressing" uses another slot in the table.
(Another strategy is "chaining")
For example, "linear probing" algorithm uses next entry.
※Python uses more complex probing, but I use simpler way in this example.
"foo"
42
"spam"
"bar"
52
"ham"
"baz"
58
"egg"
Key
hash
value
0 1 2 3 4 5 6 7
x = d["baz"]
hash("baz") = 58
58 % 8 = 2 (conflict with dummy, then linear probing)
"bar"
52
"ham"
"baz"
58
"egg"
DUMMY
Slide 18
Slide 18 text
Problems in classical open addressing hash table
● Large memory usage
○ At least 1/3 of entries are empty
■ Otherwise, "probing" can be too slow
○ One entry uses 3 words
■ word = 8 bytes on recent machine
○ minimum size = 192 byte
■ 8 (byte/word) * 3 (word/entry) * 8 (table width)
Slide 19
Slide 19 text
Python 3.6 implementation
Slide 20
Slide 20 text
Compact and ordered dict
PyPy implements it in 2015
https://morepypy.blogspot.jp/2015/01/faster-more-memory-efficient-and-more.html
Python 3.6 dict is almost same as PyPy.
Ruby 2.4, php 7 has similar one.
● Less memory usage
○ Index can be 1 byte for small dict
○ 3*8 *5 (entries) + 8 (index table) = 128 bytes
■ It was 192 bytes in legacy implementation
● Faster iteration (dense entries)
● Preserve insertion order
● (cons) One more indirect memory access
New dict vs Legacy dict
Slide 25
Slide 25 text
Toward Python 3.7
Slide 26
Slide 26 text
Working on ...
● Remove redundant code for optimize legacy
implementation.
● OrderedDict based on New dict
○ Remove doubly linked list used for keep order
○ About 1/2 memory usage!
○ Faster creation and iterating.
○ (cons) Slower .move_to_end() method
Slide 27
Slide 27 text
We're finding new contributors
Contributing to Python is easier, thanks to Github.
● Read devguide (https://devguide.python.org/ )
● Find easy bug on https://bugs.python.org/ and fix it.
● Review other's code
● Translate document on Transifex
○ See https://docs.python.org/ja/
Slide 28
Slide 28 text
No content
Slide 29
Slide 29 text
No content
Slide 30
Slide 30 text
Future ideas
● specialized dict for namespace
○ all keys are interned string
○ only pointer comparison
○ no "hash" in entry -> more compact
● Implement set like dict
○ current set is larger than dict...
● functools.lru_cache
○ Use `od.move_to_end(key)`, instead of linked list
Slide 31
Slide 31 text
PEP 412: Key sharing dict
Slide 32
Slide 32 text
PEP 412: Key sharing dict
Introduced in Python 3.4
Instances of same class can share keys object
Slide 33
Slide 33 text
class A:
def __init__(self, a, b):
self.foo = a
self.bar = b
a = A("spam", "ham")
b = A("bacon", "egg")
Slide 34
Slide 34 text
Key
Class
value
0 1 2 3 4 5 6 7
"bar"
52
"foo"
42
0 1
index
"ham"
"spam"
values
"egg"
"bacon"
values
instance
instance
Slide 35
Slide 35 text
Problem
● Two instances can have different insertion order
○ drop key sharing dict?
■ key sharing dict can save more memory.
● But __slots__ can be used for such cases!
■ performance improvements in some microbench
● Is it matter for real case? __slots__?
■ Needs consensus
● it's more difficult than implementation
Slide 36
Slide 36 text
Keep key sharing dict support
● Only exactly same order can be permitted
○ "skipped" keys are prohibited
○ deletion is also prohibited
● Otherwise, stop "key sharing"
○ `self.x = None` is faster than `del self.x`