Skip to content

Commit f8a95df

Browse files
authored
bpo-44206: Add a version number to dictionary keys (GH-26333)
* Store log2(size) instead of size in dict-keys. * Use enum instead of function pointer to record kind of keys. * Add version number to dict keys.
1 parent 8994e9c commit f8a95df

File tree

7 files changed

+223
-317
lines changed

7 files changed

+223
-317
lines changed

Include/cpython/dictobject.h

+4
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,7 @@ typedef struct {
8282

8383
PyAPI_FUNC(PyObject *) _PyDictView_New(PyObject *, PyTypeObject *);
8484
PyAPI_FUNC(PyObject *) _PyDictView_Intersect(PyObject* self, PyObject *other);
85+
86+
/* Gets a version number unique to the current state of the keys of dict, if possible.
87+
* Returns the version number, or zero if it was not possible to get a version number. */
88+
uint32_t _PyDictKeys_GetVersionForCurrentState(PyDictObject *dict);

Lib/test/test_ordered_dict.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,7 @@ def test_sizeof_exact(self):
752752
check = self.check_sizeof
753753

754754
basicsize = size('nQ2P' + '3PnPn2P')
755-
keysize = calcsize('2nP2n')
755+
keysize = calcsize('n2BI2n')
756756

757757
entrysize = calcsize('n2P')
758758
p = calcsize('P')

Lib/test/test_sys.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
# strings to intern in test_intern()
2323
INTERN_NUMRUNS = 0
2424

25+
DICT_KEY_STRUCT_FORMAT = 'n2BI2n'
2526

2627
class DisplayHookTest(unittest.TestCase):
2728

@@ -1229,9 +1230,9 @@ def inner():
12291230
# empty dict
12301231
check({}, size('nQ2P'))
12311232
# dict
1232-
check({"a": 1}, size('nQ2P') + calcsize('2nP2n') + 8 + (8*2//3)*calcsize('n2P'))
1233+
check({"a": 1}, size('nQ2P') + calcsize(DICT_KEY_STRUCT_FORMAT) + 8 + (8*2//3)*calcsize('n2P'))
12331234
longdict = {1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8}
1234-
check(longdict, size('nQ2P') + calcsize('2nP2n') + 16 + (16*2//3)*calcsize('n2P'))
1235+
check(longdict, size('nQ2P') + calcsize(DICT_KEY_STRUCT_FORMAT) + 16 + (16*2//3)*calcsize('n2P'))
12351236
# dictionary-keyview
12361237
check({}.keys(), size('P'))
12371238
# dictionary-valueview
@@ -1385,13 +1386,13 @@ def delx(self): del self.__x
13851386
'5P')
13861387
class newstyleclass(object): pass
13871388
# Separate block for PyDictKeysObject with 8 keys and 5 entries
1388-
check(newstyleclass, s + calcsize("2nP2n0P") + 8 + 5*calcsize("n2P"))
1389+
check(newstyleclass, s + calcsize(DICT_KEY_STRUCT_FORMAT) + 8 + 5*calcsize("n2P"))
13891390
# dict with shared keys
13901391
check(newstyleclass().__dict__, size('nQ2P') + 5*self.P)
13911392
o = newstyleclass()
13921393
o.a = o.b = o.c = o.d = o.e = o.f = o.g = o.h = 1
13931394
# Separate block for PyDictKeysObject with 16 keys and 10 entries
1394-
check(newstyleclass, s + calcsize("2nP2n0P") + 16 + 10*calcsize("n2P"))
1395+
check(newstyleclass, s + calcsize(DICT_KEY_STRUCT_FORMAT) + 16 + 10*calcsize("n2P"))
13951396
# dict with shared keys
13961397
check(newstyleclass().__dict__, size('nQ2P') + 10*self.P)
13971398
# unicode

Objects/dict-common.h

+14-17
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,34 @@ typedef struct {
88
PyObject *me_value; /* This field is only meaningful for combined tables */
99
} PyDictKeyEntry;
1010

11-
/* dict_lookup_func() returns index of entry which can be used like DK_ENTRIES(dk)[index].
11+
/* _Py_dict_lookup() returns index of entry which can be used like DK_ENTRIES(dk)[index].
1212
* -1 when no entry found, -3 when compare raises error.
1313
*/
14-
typedef Py_ssize_t (*dict_lookup_func)
15-
(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr);
14+
Py_ssize_t _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr);
15+
1616

1717
#define DKIX_EMPTY (-1)
1818
#define DKIX_DUMMY (-2) /* Used internally */
1919
#define DKIX_ERROR (-3)
2020

21+
typedef enum {
22+
DICT_KEYS_GENERAL = 0,
23+
DICT_KEYS_UNICODE = 1,
24+
DICT_KEYS_SPLIT = 2
25+
} DictKeysKind;
26+
2127
/* See dictobject.c for actual layout of DictKeysObject */
2228
struct _dictkeysobject {
2329
Py_ssize_t dk_refcnt;
2430

2531
/* Size of the hash table (dk_indices). It must be a power of 2. */
26-
Py_ssize_t dk_size;
27-
28-
/* Function to lookup in the hash table (dk_indices):
29-
30-
- lookdict(): general-purpose, and may return DKIX_ERROR if (and
31-
only if) a comparison raises an exception.
32-
33-
- lookdict_unicode(): specialized to Unicode string keys, comparison of
34-
which can never raise an exception; that function can never return
35-
DKIX_ERROR.
32+
uint8_t dk_log2_size;
3633

37-
- lookdict_unicode_nodummy(): similar to lookdict_unicode() but further
38-
specialized for Unicode string keys that cannot be the <dummy> value.
34+
/* Kind of keys */
35+
uint8_t dk_kind;
3936

40-
- lookdict_split(): Version of lookdict() for split tables. */
41-
dict_lookup_func dk_lookup;
37+
/* Version number -- Reset to 0 by any modification to keys */
38+
uint32_t dk_version;
4239

4340
/* Number of usable entries in dk_entries. */
4441
Py_ssize_t dk_usable;

0 commit comments

Comments
 (0)