From ee77d727f829f7b5f0886b7acba5547584035229 Mon Sep 17 00:00:00 2001 From: LucasZhou Date: Sun, 4 Oct 2026 20:04:05 -0500 Subject: [PATCH] gh-158816: Skip redundant hash table probe when deleting from split dicts delitem_common() unconditionally called lookdict_index() to locate the index slot in dk_indices that points at the entry being removed. That slot is only used by the combined branch, to overwrite it with DKIX_DUMMY. In split tables the whole probe sequence was redundant work. Move the call into the combined branch and drop the now-pointless assert() for split tables. Microbenchmark (free-threaded build, -Og, no PGO; deleting keys from materialized instance dicts; medians over 12 alternating runs): del d[k] 71.2 -> 69.2 ns/op (-2.8%) d.pop(k) 98.9 -> 96.1 ns/op (-2.8%) colliding hashes 78.0 -> 74.4 ns/op (-4.6%) clang still does not sink the call into the branch at -O3 -DNDEBUG, so this is not specific to the local -Og build. test_dict, test_dictviews and test_dictcomps pass. --- .../2026-10-04-20-04-55.gh-issue-158816.9ulhWb.rst | 3 +++ Objects/dictobject.c | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-10-04-20-04-55.gh-issue-158816.9ulhWb.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-04-20-04-55.gh-issue-158816.9ulhWb.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-04-20-04-55.gh-issue-158816.9ulhWb.rst new file mode 100644 index 000000000000000..fd9997afb3e1d34 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-04-20-04-55.gh-issue-158816.9ulhWb.rst @@ -0,0 +1,3 @@ +Speed up deleting items from split dictionaries (for example the +materialized ``__dict__`` of an instance with inline values) by skipping a +redundant hash table probe. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index c6d055a412a515b..94151a9c18d4f8b 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -2938,9 +2938,6 @@ delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix, PyObject *old_key; - Py_ssize_t hashpos = lookdict_index(mp->ma_keys, hash, ix); - assert(hashpos >= 0); - STORE_USED(mp, mp->ma_used - 1); if (_PyDict_HasSplitTable(mp)) { assert(old_value == mp->ma_values->values[ix]); @@ -2951,6 +2948,9 @@ delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix, ASSERT_CONSISTENT(mp); } else { + Py_ssize_t hashpos = lookdict_index(mp->ma_keys, hash, ix); + assert(hashpos >= 0); + FT_ATOMIC_STORE_UINT32_RELAXED(mp->ma_keys->dk_version, 0); dictkeys_set_index(mp->ma_keys, hashpos, DKIX_DUMMY); if (DK_IS_UNICODE(mp->ma_keys)) {