Split-Table vs Combined-Table Dictionaries in CPython

CPython utilizes two distinct internal storage strategies for dictionaries: combined-table dictionaries and split-table dictionaries. Introduced in PEP 412 (Key-Sharing Dictionary), this distinction optimizes memory consumption across object-oriented programs. While combined-table dictionaries store keys, hash values, and values together for general-purpose use, split-table dictionaries separate the shared keys from instance-specific values, drastically reducing the memory footprint of multiple instances of the same class.

The Underlying Dict Architecture in CPython

In modern CPython (3.6+), a standard dictionary consists of two primary internal arrays:

  1. Hash Indices Table: A sparse array mapping hash values to entries.
  2. Entries Table: A dense array of PyDictKeyEntry structs containing the hash, the key pointer, and the value pointer.

The C-level representation is governed by the PyDictObject structure, which contains pointers to a keys object (PyDictKeysObject *ma_keys) and an optional values array (PyObject **ma_values).

Combined-Table Dictionaries

A combined-table dictionary is the default layout for standard dictionaries, such as those created using dictionary literals ({}) or dict().

Split-Table Dictionaries (Key-Sharing Dictionaries)

Split-table dictionaries are designed primarily for object attribute dictionaries (instance.__dict__). In typical object-oriented code, hundreds or thousands of instances of a class share the exact same attribute names in the exact same initialization order, differing only in the values assigned to those attributes.

Key Architectural Differences

Feature Combined-Table Dict Split-Table Dict
ma_values Pointer Set to NULL Points to a separate array of PyObject*
ma_keys Ownership Owned exclusively by the dict instance Shared across multiple dict instances
Memory per Instance High (stores hashes, keys, and values) Low (stores only an array of value pointers)
Insertion Mechanism Modifies the local hash table and entry array Requires keys to match the pre-existing shared layout

Mutability and Table Conversion

A split-table dictionary requires that all participating instances preserve an identical key ordering. CPython manages this dynamically:

  1. Creation: When an instance is initialized, its __dict__ is allocated as a split-table dictionary sharing keys with the type.
  2. Mutation: As attributes are assigned in __init__, the shared PyDictKeysObject registers the keys.
  3. De-optimization (Combined Conversion): If an individual instance adds an attribute that was not registered in the shared keys table, or if an attribute is deleted (del inst.attr), the instance can no longer share the key layout. CPython automatically converts that specific instance's dictionary into a combined-table dictionary, allocating a private PyDictKeysObject and merging the values back into it. Once converted to a combined table, an instance dictionary cannot revert to a split table.