From f7f967e9f6f4514654253a1c2c94685ec4b3bfa3 Mon Sep 17 00:00:00 2001 From: Brij <97006829+bkap123@users.noreply.github.com> Date: Sat, 30 May 2026 10:48:52 -0400 Subject: [PATCH 1/5] gh-142889: Improve layout of dictionary keys --- Include/internal/pycore_dict.h | 39 ++++----- Lib/test/test_dict.py | 15 ++++ Modules/Setup.stdlib.in | 2 +- Modules/_testinternalcapi.c | 3 + Modules/_testinternalcapi/dict.c | 50 ++++++++++++ Modules/_testinternalcapi/parts.h | 1 + Objects/dictobject.c | 99 ++++++++++++++--------- PCbuild/_testinternalcapi.vcxproj | 1 + PCbuild/_testinternalcapi.vcxproj.filters | 3 + 9 files changed, 152 insertions(+), 61 deletions(-) create mode 100644 Modules/_testinternalcapi/dict.c diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index ff6588b3e9718c..d2ccc387b0701c 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -219,24 +219,10 @@ struct _dictkeysobject { /* Number of used entries in dk_entries. */ Py_ssize_t dk_nentries; - - /* Actual hash table of dk_size entries. It holds indices in dk_entries, - or DKIX_EMPTY(-1) or DKIX_DUMMY(-2). - - Indices must be: 0 <= indice < USABLE_FRACTION(dk_size). - - The size in bytes of an indice depends on dk_size: - - - 1 byte if dk_size <= 0xff (char*) - - 2 bytes if dk_size <= 0xffff (int16_t*) - - 4 bytes if dk_size <= 0xffffffff (int32_t*) - - 8 bytes otherwise (int64_t*) - - Dynamically sized, SIZEOF_VOID_P is minimum. */ - char dk_indices[]; /* char is required to avoid strict aliasing. */ - - /* "PyDictKeyEntry or PyDictUnicodeEntry dk_entries[USABLE_FRACTION(DK_SIZE(dk))];" array follows: - see the DK_ENTRIES() / DK_UNICODE_ENTRIES() functions below */ + union { + PyDictKeyEntry entries[]; + PyDictUnicodeEntry unicode_entries[]; + } dk_entries; }; /* This must be no more than 250, for the prefix size to fit in one byte. */ @@ -258,25 +244,32 @@ struct _dictvalues { }; #define DK_LOG_SIZE(dk) _Py_RVALUE((dk)->dk_log2_size) +#define DK_LOG_INDEX_BYTES(dk) \ + _Py_RVALUE((dk)->dk_log2_index_bytes) #if SIZEOF_VOID_P > 4 #define DK_SIZE(dk) (((int64_t)1)<dk_indices); - size_t index = (size_t)1 << dk->dk_log2_index_bytes; - return (&indices[index]); + return (void *)(&dk->dk_entries); } static inline PyDictKeyEntry* DK_ENTRIES(PyDictKeysObject *dk) { assert(dk->dk_kind == DICT_KEYS_GENERAL); - return (PyDictKeyEntry*)_DK_ENTRIES(dk); + return dk->dk_entries.entries; } + static inline PyDictUnicodeEntry* DK_UNICODE_ENTRIES(PyDictKeysObject *dk) { assert(dk->dk_kind != DICT_KEYS_GENERAL); - return (PyDictUnicodeEntry*)_DK_ENTRIES(dk); + return dk->dk_entries.unicode_entries; } #define DK_IS_UNICODE(dk) ((dk)->dk_kind != DICT_KEYS_GENERAL) diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index f26586809238f0..b2ad543bcea56f 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -1794,6 +1794,21 @@ def __hash__(self): self.assertEqual(dict_getitem_knownhash(d, k1, hash(k1)), 1) self.assertRaises(Exc, dict_getitem_knownhash, d, k2, hash(k2)) + @support.cpython_only + def test_indices_layout(self): + _testinternalcapi = import_helper.import_module('_testinternalcapi') + check_layout = _testinternalcapi.test_dict_keys_layout + + for i in range(4): + self.assertTrue(check_layout({j: j for j in range(10**i)})) + + @support.cpython_only + def test_dict_keys_to_base(self): + _testinternalcapi = import_helper.import_module('_testinternalcapi') + check_base = _testinternalcapi.test_dict_keys_to_base + + for i in range(4): + self.assertTrue(check_base({j: j for j in range(10**i)})) from test import mapping_tests diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index c3dd47a5e40a67..e3e1b9834c1695 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -172,7 +172,7 @@ @MODULE_XXSUBTYPE_TRUE@xxsubtype xxsubtype.c @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c -@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c +@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c _testinternalcapi/dict.c @MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c @MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index d0d1f1f1bc8e53..77d751022f5522 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -3352,6 +3352,9 @@ module_exec(PyObject *module) if (_PyTestInternalCapi_Init_Tuple(module) < 0) { return 1; } + if (_PyTestInternalCapi_Init_Dict(module) < 0) { + return 1; + } Py_ssize_t sizeof_gc_head = 0; #ifndef Py_GIL_DISABLED diff --git a/Modules/_testinternalcapi/dict.c b/Modules/_testinternalcapi/dict.c new file mode 100644 index 00000000000000..29f11c6c35ffed --- /dev/null +++ b/Modules/_testinternalcapi/dict.c @@ -0,0 +1,50 @@ +#include "parts.h" + +#include "pycore_dict.h" + +static PyObject* +test_dict_keys_layout(PyObject *self, PyObject *arg) +{ + PyDictObject *mp = (PyDictObject *)arg; + PyDictKeysObject *keys = mp->ma_keys; + + size_t indices_size = DK_INDEX_BYTES(keys); + + char *base = _DK_INDICES(keys); + char *header = (char *)keys; + char *entries = (char *)_DK_ENTRIES(keys); + + bool ok = true; + ok &= (header == base + indices_size); + ok &= (entries == header + offsetof(PyDictKeysObject, dk_entries)); + + return PyBool_FromLong(ok); +} + +static PyObject* +test_dict_keys_to_base(PyObject *self, PyObject *arg) +{ + PyDictObject *mp = (PyDictObject *)arg; + PyDictKeysObject *keys = mp->ma_keys; + + void *base = _DK_INDICES(keys); + size_t indices_size = DK_INDEX_BYTES(keys); + bool ok = _DK_FROM_BASE(base, indices_size) == keys; + + return PyBool_FromLong(ok); +} + +static PyMethodDef test_methods[] = { + {"test_dict_keys_layout", test_dict_keys_layout, METH_O}, + {"test_dict_keys_to_base", test_dict_keys_to_base, METH_O}, + {NULL}, +}; + +int +_PyTestInternalCapi_Init_Dict(PyObject *m) +{ + if (PyModule_AddFunctions(m, test_methods) < 0) { + return -1; + } + return 0; +} diff --git a/Modules/_testinternalcapi/parts.h b/Modules/_testinternalcapi/parts.h index 81f536c3babb18..f12fbd27883f6f 100644 --- a/Modules/_testinternalcapi/parts.h +++ b/Modules/_testinternalcapi/parts.h @@ -16,5 +16,6 @@ int _PyTestInternalCapi_Init_Set(PyObject *module); int _PyTestInternalCapi_Init_Complex(PyObject *module); int _PyTestInternalCapi_Init_CriticalSection(PyObject *module); int _PyTestInternalCapi_Init_Tuple(PyObject *module); +int _PyTestInternalCapi_Init_Dict(PyObject *module); #endif // Py_TESTINTERNALCAPI_PARTS_H diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 66546b72130dd0..adf1a1765297f7 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -17,6 +17,9 @@ As of Python 3.6, this is compact and ordered. Basic idea is described here: layout: +---------------------+ +| dk_indices[] | +| | ++---------------------+ <---- PyDictKeysObject* pointer points here | dk_refcnt | | dk_log2_size | | dk_log2_index_bytes | @@ -25,15 +28,18 @@ As of Python 3.6, this is compact and ordered. Basic idea is described here: | dk_usable | | dk_nentries | +---------------------+ -| dk_indices[] | -| | -+---------------------+ | dk_entries[] | | | +---------------------+ -dk_indices is actual hashtable. It holds index in entries, or DKIX_EMPTY(-1) -or DKIX_DUMMY(-2). + + +The layout of the dictionary keys consists of three sections: +the indicies (the actual hashtable), the header, and the entries. +PyDictKeysObject * points to the header of the struct. + +The dk_indices table is stored immediately before the header in memory (see gh-142889). +It holds index in entries, or DKIX_EMPTY(-1) or DKIX_DUMMY(-2). Size of indices is dk_size. Type of each index in indices varies with dk_size: * int8 for dk_size <= 128 @@ -177,8 +183,10 @@ ASSERT_DICT_LOCKED(PyObject *op) #define IS_DICT_SHARED(mp) _PyObject_GC_IS_SHARED(mp) #define SET_DICT_SHARED(mp) _PyObject_GC_SET_SHARED(mp) -#define LOAD_INDEX(keys, size, idx) _Py_atomic_load_int##size##_relaxed(&((const int##size##_t*)keys->dk_indices)[idx]); -#define STORE_INDEX(keys, size, idx, value) _Py_atomic_store_int##size##_relaxed(&((int##size##_t*)keys->dk_indices)[idx], (int##size##_t)value); +#define LOAD_INDEX(keys, size, idx) \ + _Py_atomic_load_int##size##_relaxed(&((const int##size##_t*)(_DK_INDICES(keys)))[idx]); +#define STORE_INDEX(keys, size, idx, value) \ + _Py_atomic_store_int##size##_relaxed(&((int##size##_t*)(_DK_INDICES(keys)))[idx], (int##size##_t)value); #define ASSERT_OWNED_OR_SHARED(mp) \ assert(_Py_IsOwnedByCurrentThread((PyObject *)mp) || IS_DICT_SHARED(mp)); @@ -257,8 +265,8 @@ static inline void split_keys_entry_added(PyDictKeysObject *keys) #define UNLOCK_KEYS_IF_SPLIT(keys, kind) #define IS_DICT_SHARED(mp) (false) #define SET_DICT_SHARED(mp) -#define LOAD_INDEX(keys, size, idx) ((const int##size##_t*)(keys->dk_indices))[idx] -#define STORE_INDEX(keys, size, idx, value) ((int##size##_t*)(keys->dk_indices))[idx] = (int##size##_t)value +#define LOAD_INDEX(keys, size, idx) ((const int##size##_t*)(_DK_INDICES(keys)))[idx] +#define STORE_INDEX(keys, size, idx, value) ((int##size##_t*)(_DK_INDICES(keys)))[idx] = (int##size##_t)value static inline void split_keys_entry_added(PyDictKeysObject *keys) { @@ -627,22 +635,31 @@ estimate_log2_keysize(Py_ssize_t n) * See https://github.com/python/cpython/pull/127568#discussion_r1868070614 * for the rationale of using dk_log2_index_bytes=3 instead of 0. */ -static PyDictKeysObject empty_keys_struct = { - _Py_DICT_IMMORTAL_INITIAL_REFCNT, /* dk_refcnt */ - 0, /* dk_log2_size */ - 3, /* dk_log2_index_bytes */ - DICT_KEYS_UNICODE, /* dk_kind */ +typedef struct { + int8_t indices[8]; + PyDictKeysObject keys; +} _PyDict_EmptyKeys; + +static _PyDict_EmptyKeys empty_keys = { + .indices = { + DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY, + DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY, + }, + .keys = { + .dk_refcnt = _Py_DICT_IMMORTAL_INITIAL_REFCNT, + .dk_log2_size = 0, + .dk_log2_index_bytes = 3, + .dk_kind = DICT_KEYS_UNICODE, #ifdef Py_GIL_DISABLED - {0}, /* dk_mutex */ + .dk_mutex = {0}, #endif - 1, /* dk_version */ - 0, /* dk_usable (immutable) */ - 0, /* dk_nentries */ - {DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY, - DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY}, /* dk_indices */ + .dk_version = 1, + .dk_usable = 0, /* immutable */ + .dk_nentries = 0, + }, }; -#define Py_EMPTY_KEYS &empty_keys_struct +#define Py_EMPTY_KEYS &empty_keys.keys /* Uncomment to check the dict content in _PyDict_CheckConsistency() */ // #define DEBUG_PYDICT @@ -809,19 +826,22 @@ new_keys_object(uint8_t log2_size, bool unicode) log2_bytes = log2_size + 2; } - PyDictKeysObject *dk = NULL; + void *base = NULL; + size_t indices_size = (size_t)1 << log2_bytes; if (log2_size == PyDict_LOG_MINSIZE && unicode) { - dk = _Py_FREELIST_POP_MEM(dictkeys); + base = _Py_FREELIST_POP_MEM(dictkeys); } - if (dk == NULL) { - dk = PyMem_Malloc(sizeof(PyDictKeysObject) - + ((size_t)1 << log2_bytes) - + entry_size * usable); - if (dk == NULL) { + if (base == NULL) { + base = PyMem_Malloc(sizeof(PyDictKeysObject) + + indices_size + entry_size * usable); + if (base == NULL) { PyErr_NoMemory(); return NULL; } } + + PyDictKeysObject *dk = _DK_FROM_BASE(base, indices_size); + #ifdef Py_REF_DEBUG _Py_IncRefTotal(_PyThreadState_GET()); #endif @@ -835,25 +855,26 @@ new_keys_object(uint8_t log2_size, bool unicode) dk->dk_nentries = 0; dk->dk_usable = usable; dk->dk_version = 0; - memset(&dk->dk_indices[0], 0xff, ((size_t)1 << log2_bytes)); - memset(&dk->dk_indices[(size_t)1 << log2_bytes], 0, entry_size * usable); + memset(_DK_INDICES(dk), 0xff, indices_size); + memset(&dk->dk_entries, 0, entry_size * usable); return dk; } static void free_keys_object(PyDictKeysObject *keys, bool use_qsbr) { + void *base = _DK_INDICES(keys); #ifdef Py_GIL_DISABLED if (use_qsbr) { - _PyMem_FreeDelayed(keys, _PyDict_KeysSize(keys)); + _PyMem_FreeDelayed(base, _PyDict_KeysSize(keys)); return; } #endif if (DK_LOG_SIZE(keys) == PyDict_LOG_MINSIZE && keys->dk_kind == DICT_KEYS_UNICODE) { - _Py_FREELIST_FREE(dictkeys, keys, PyMem_Free); + _Py_FREELIST_FREE(dictkeys, base, PyMem_Free); } else { - PyMem_Free(keys); + PyMem_Free(base); } } @@ -975,14 +996,18 @@ clone_combined_dict_keys(PyDictObject *orig) ASSERT_DICT_LOCKED(orig); } - size_t keys_size = _PyDict_KeysSize(orig->ma_keys); - PyDictKeysObject *keys = PyMem_Malloc(keys_size); - if (keys == NULL) { + PyDictKeysObject *orig_keys = orig->ma_keys; + size_t keys_size = _PyDict_KeysSize(orig_keys); + size_t indices_size = DK_INDEX_BYTES(orig_keys); + + void *base = PyMem_Malloc(keys_size); + if (base == NULL) { PyErr_NoMemory(); return NULL; } - memcpy(keys, orig->ma_keys, keys_size); + PyDictKeysObject *keys = _DK_FROM_BASE(base, indices_size); + memcpy(base, _DK_INDICES(orig_keys), keys_size); /* After copying key/value pairs, we need to incref all keys and values and they are about to be co-owned by a diff --git a/PCbuild/_testinternalcapi.vcxproj b/PCbuild/_testinternalcapi.vcxproj index f3e423fa04668e..ba35b916c0c597 100644 --- a/PCbuild/_testinternalcapi.vcxproj +++ b/PCbuild/_testinternalcapi.vcxproj @@ -101,6 +101,7 @@ + diff --git a/PCbuild/_testinternalcapi.vcxproj.filters b/PCbuild/_testinternalcapi.vcxproj.filters index 7ab242c2c230b6..42a609d191266c 100644 --- a/PCbuild/_testinternalcapi.vcxproj.filters +++ b/PCbuild/_testinternalcapi.vcxproj.filters @@ -30,6 +30,9 @@ Source Files + + Source Files + From 66659cf7fd4e9e69837d46c4fa7a25133a20d24d Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 30 May 2026 21:37:55 +0000 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-05-30-21-37-54.gh-issue-142889.LvkzYw.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-05-30-21-37-54.gh-issue-142889.LvkzYw.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-30-21-37-54.gh-issue-142889.LvkzYw.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-30-21-37-54.gh-issue-142889.LvkzYw.rst new file mode 100644 index 00000000000000..adfad76b4a771f --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-30-21-37-54.gh-issue-142889.LvkzYw.rst @@ -0,0 +1 @@ +Update the memory layout of how :class:`dict` handles the memory of the keys to improve performance. From 13b8d7eb23a9e4c7f2477ad6b992e5739507f38f Mon Sep 17 00:00:00 2001 From: Brij <97006829+bkap123@users.noreply.github.com> Date: Sat, 30 May 2026 17:46:03 -0400 Subject: [PATCH 3/5] update union --- Include/internal/pycore_dict.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index d2ccc387b0701c..0d932fd615c970 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -220,8 +220,8 @@ struct _dictkeysobject { Py_ssize_t dk_nentries; union { - PyDictKeyEntry entries[]; - PyDictUnicodeEntry unicode_entries[]; + PyDictKeyEntry entries[1]; + PyDictUnicodeEntry unicode_entries[1]; } dk_entries; }; From 44768445f683eef7b4c69ac01f67039d73b30549 Mon Sep 17 00:00:00 2001 From: Brij <97006829+bkap123@users.noreply.github.com> Date: Sun, 31 May 2026 07:10:57 -0400 Subject: [PATCH 4/5] Update test/gdb --- Lib/test/test_dict.py | 6 +++--- Modules/_testinternalcapi/dict.c | 8 ++++---- Tools/gdb/libpython.py | 14 +------------- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index b2ad543bcea56f..22568916097b9b 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -1795,9 +1795,9 @@ def __hash__(self): self.assertRaises(Exc, dict_getitem_knownhash, d, k2, hash(k2)) @support.cpython_only - def test_indices_layout(self): + def test_dict_keys_layout(self): _testinternalcapi = import_helper.import_module('_testinternalcapi') - check_layout = _testinternalcapi.test_dict_keys_layout + check_layout = _testinternalcapi.dict_keys_layout for i in range(4): self.assertTrue(check_layout({j: j for j in range(10**i)})) @@ -1805,7 +1805,7 @@ def test_indices_layout(self): @support.cpython_only def test_dict_keys_to_base(self): _testinternalcapi = import_helper.import_module('_testinternalcapi') - check_base = _testinternalcapi.test_dict_keys_to_base + check_base = _testinternalcapi.dict_keys_to_base for i in range(4): self.assertTrue(check_base({j: j for j in range(10**i)})) diff --git a/Modules/_testinternalcapi/dict.c b/Modules/_testinternalcapi/dict.c index 29f11c6c35ffed..c59303fa9d80b3 100644 --- a/Modules/_testinternalcapi/dict.c +++ b/Modules/_testinternalcapi/dict.c @@ -3,7 +3,7 @@ #include "pycore_dict.h" static PyObject* -test_dict_keys_layout(PyObject *self, PyObject *arg) +dict_keys_layout(PyObject *self, PyObject *arg) { PyDictObject *mp = (PyDictObject *)arg; PyDictKeysObject *keys = mp->ma_keys; @@ -22,7 +22,7 @@ test_dict_keys_layout(PyObject *self, PyObject *arg) } static PyObject* -test_dict_keys_to_base(PyObject *self, PyObject *arg) +dict_keys_to_base(PyObject *self, PyObject *arg) { PyDictObject *mp = (PyDictObject *)arg; PyDictKeysObject *keys = mp->ma_keys; @@ -35,8 +35,8 @@ test_dict_keys_to_base(PyObject *self, PyObject *arg) } static PyMethodDef test_methods[] = { - {"test_dict_keys_layout", test_dict_keys_layout, METH_O}, - {"test_dict_keys_to_base", test_dict_keys_to_base, METH_O}, + {"dict_keys_layout", dict_keys_layout, METH_O}, + {"dict_keys_to_base", dict_keys_to_base, METH_O}, {NULL}, }; diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py index ba52ea2a30e0be..499aee08d8cbbe 100755 --- a/Tools/gdb/libpython.py +++ b/Tools/gdb/libpython.py @@ -846,25 +846,13 @@ def write_repr(self, out, visited): @staticmethod def _get_entries(keys): dk_nentries = int(keys['dk_nentries']) - dk_size = 1< Date: Sun, 31 May 2026 07:46:16 -0400 Subject: [PATCH 5/5] update sizeof calculation --- Objects/dictobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index adf1a1765297f7..dd554181a60a4a 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -5058,7 +5058,7 @@ _PyDict_KeysSize(PyDictKeysObject *keys) { size_t es = (keys->dk_kind == DICT_KEYS_GENERAL ? sizeof(PyDictKeyEntry) : sizeof(PyDictUnicodeEntry)); - size_t size = sizeof(PyDictKeysObject); + size_t size = sizeof(PyDictKeysObject) - sizeof(PyDictKeyEntry); size += (size_t)1 << keys->dk_log2_index_bytes; size += USABLE_FRACTION((size_t)DK_SIZE(keys)) * es; return size;