Skip to content

Commit 121057a

Browse files
authored
GH-89987: Shrink the BINARY_SUBSCR caches (GH-103022)
1 parent e647dba commit 121057a

File tree

14 files changed

+272
-250
lines changed

14 files changed

+272
-250
lines changed

Include/cpython/object.h

+11
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,18 @@ struct _typeobject {
234234
* It should should be treated as an opaque blob
235235
* by code other than the specializer and interpreter. */
236236
struct _specialization_cache {
237+
// In order to avoid bloating the bytecode with lots of inline caches, the
238+
// members of this structure have a somewhat unique contract. They are set
239+
// by the specialization machinery, and are invalidated by PyType_Modified.
240+
// The rules for using them are as follows:
241+
// - If getitem is non-NULL, then it is the same Python function that
242+
// PyType_Lookup(cls, "__getitem__") would return.
243+
// - If getitem is NULL, then getitem_version is meaningless.
244+
// - If getitem->func_version == getitem_version, then getitem can be called
245+
// with two positional arguments and no keyword arguments, and has neither
246+
// *args nor **kwargs (as required by BINARY_SUBSCR_GETITEM):
237247
PyObject *getitem;
248+
uint32_t getitem_version;
238249
};
239250

240251
/* The *real* layout of a type object when allocated on the heap */

Include/internal/pycore_code.h

-2
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ typedef struct {
4747

4848
typedef struct {
4949
uint16_t counter;
50-
uint16_t type_version[2];
51-
uint16_t func_version;
5250
} _PyBinarySubscrCache;
5351

5452
#define INLINE_CACHE_ENTRIES_BINARY_SUBSCR CACHE_ENTRIES(_PyBinarySubscrCache)

Include/internal/pycore_opcode.h

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/importlib/_bootstrap_external.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,9 @@ def _write_atomic(path, data, mode=0o666):
435435
# Python 3.12a6 3519 (Modify SEND instruction)
436436
# Python 3.12a6 3520 (Remove PREP_RERAISE_STAR, add CALL_INTRINSIC_2)
437437
# Python 3.12a7 3521 (Shrink the LOAD_GLOBAL caches)
438+
# Python 3.12a7 3522 (Removed JUMP_IF_FALSE_OR_POP/JUMP_IF_TRUE_OR_POP)
438439
# Python 3.12a7 3523 (Convert COMPARE_AND_BRANCH back to COMPARE_OP)
440+
# Python 3.12a7 3524 (Shrink the BINARY_SUBSCR caches)
439441

440442
# Python 3.13 will start with 3550
441443

@@ -452,7 +454,7 @@ def _write_atomic(path, data, mode=0o666):
452454
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
453455
# in PC/launcher.c must also be updated.
454456

455-
MAGIC_NUMBER = (3523).to_bytes(2, 'little') + b'\r\n'
457+
MAGIC_NUMBER = (3524).to_bytes(2, 'little') + b'\r\n'
456458

457459
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
458460

Lib/opcode.py

-2
Original file line numberDiff line numberDiff line change
@@ -392,8 +392,6 @@ def pseudo_op(name, op, real_ops):
392392
},
393393
"BINARY_SUBSCR": {
394394
"counter": 1,
395-
"type_version": 2,
396-
"func_version": 1,
397395
},
398396
"FOR_ITER": {
399397
"counter": 1,

Lib/test/test_dis.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1108,7 +1108,7 @@ def test_binary_specialize(self):
11081108
1 2 LOAD_NAME 0 (a)
11091109
4 LOAD_CONST 0 (0)
11101110
6 %s
1111-
16 RETURN_VALUE
1111+
10 RETURN_VALUE
11121112
"""
11131113
co_list = compile('a[0]', "<list>", "eval")
11141114
self.code_quicken(lambda: exec(co_list, {}, {'a': [0]}))

Lib/test/test_sys.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1556,7 +1556,7 @@ def delx(self): del self.__x
15561556
'10P' # PySequenceMethods
15571557
'2P' # PyBufferProcs
15581558
'6P'
1559-
'1P' # Specializer cache
1559+
'1PI' # Specializer cache
15601560
)
15611561
class newstyleclass(object): pass
15621562
# Separate block for PyDictKeysObject with 8 keys and 5 entries
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Reduce the number of inline :opcode:`CACHE` entries for
2+
:opcode:`BINARY_SUBSCR`.

Objects/typeobject.c

+10
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,11 @@ PyType_Modified(PyTypeObject *type)
510510

511511
type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG;
512512
type->tp_version_tag = 0; /* 0 is not a valid version tag */
513+
if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
514+
// This field *must* be invalidated if the type is modified (see the
515+
// comment on struct _specialization_cache):
516+
((PyHeapTypeObject *)type)->_spec_cache.getitem = NULL;
517+
}
513518
}
514519

515520
static void
@@ -563,6 +568,11 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
563568
clear:
564569
type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG;
565570
type->tp_version_tag = 0; /* 0 is not a valid version tag */
571+
if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
572+
// This field *must* be invalidated if the type is modified (see the
573+
// comment on struct _specialization_cache):
574+
((PyHeapTypeObject *)type)->_spec_cache.getitem = NULL;
575+
}
566576
}
567577

568578
static int

Programs/test_frozenmain.h

+28-29
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/bytecodes.c

+11-9
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ dummy_func(
292292
BINARY_SUBSCR_TUPLE_INT,
293293
};
294294

295-
inst(BINARY_SUBSCR, (unused/4, container, sub -- res)) {
295+
inst(BINARY_SUBSCR, (unused/1, container, sub -- res)) {
296296
#if ENABLE_SPECIALIZATION
297297
_PyBinarySubscrCache *cache = (_PyBinarySubscrCache *)next_instr;
298298
if (ADAPTIVE_COUNTER_IS_ZERO(cache->counter)) {
@@ -339,7 +339,7 @@ dummy_func(
339339
ERROR_IF(err, error);
340340
}
341341

342-
inst(BINARY_SUBSCR_LIST_INT, (unused/4, list, sub -- res)) {
342+
inst(BINARY_SUBSCR_LIST_INT, (unused/1, list, sub -- res)) {
343343
assert(cframe.use_tracing == 0);
344344
DEOPT_IF(!PyLong_CheckExact(sub), BINARY_SUBSCR);
345345
DEOPT_IF(!PyList_CheckExact(list), BINARY_SUBSCR);
@@ -356,7 +356,7 @@ dummy_func(
356356
Py_DECREF(list);
357357
}
358358

359-
inst(BINARY_SUBSCR_TUPLE_INT, (unused/4, tuple, sub -- res)) {
359+
inst(BINARY_SUBSCR_TUPLE_INT, (unused/1, tuple, sub -- res)) {
360360
assert(cframe.use_tracing == 0);
361361
DEOPT_IF(!PyLong_CheckExact(sub), BINARY_SUBSCR);
362362
DEOPT_IF(!PyTuple_CheckExact(tuple), BINARY_SUBSCR);
@@ -373,7 +373,7 @@ dummy_func(
373373
Py_DECREF(tuple);
374374
}
375375

376-
inst(BINARY_SUBSCR_DICT, (unused/4, dict, sub -- res)) {
376+
inst(BINARY_SUBSCR_DICT, (unused/1, dict, sub -- res)) {
377377
assert(cframe.use_tracing == 0);
378378
DEOPT_IF(!PyDict_CheckExact(dict), BINARY_SUBSCR);
379379
STAT_INC(BINARY_SUBSCR, hit);
@@ -389,14 +389,16 @@ dummy_func(
389389
DECREF_INPUTS();
390390
}
391391

392-
inst(BINARY_SUBSCR_GETITEM, (unused/1, type_version/2, func_version/1, container, sub -- unused)) {
392+
inst(BINARY_SUBSCR_GETITEM, (unused/1, container, sub -- unused)) {
393393
PyTypeObject *tp = Py_TYPE(container);
394-
DEOPT_IF(tp->tp_version_tag != type_version, BINARY_SUBSCR);
395-
assert(tp->tp_flags & Py_TPFLAGS_HEAPTYPE);
396-
PyObject *cached = ((PyHeapTypeObject *)tp)->_spec_cache.getitem;
394+
DEOPT_IF(!PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE), BINARY_SUBSCR);
395+
PyHeapTypeObject *ht = (PyHeapTypeObject *)tp;
396+
PyObject *cached = ht->_spec_cache.getitem;
397+
DEOPT_IF(cached == NULL, BINARY_SUBSCR);
397398
assert(PyFunction_Check(cached));
398399
PyFunctionObject *getitem = (PyFunctionObject *)cached;
399-
DEOPT_IF(getitem->func_version != func_version, BINARY_SUBSCR);
400+
uint32_t cached_version = ht->_spec_cache.getitem_version;
401+
DEOPT_IF(getitem->func_version != cached_version, BINARY_SUBSCR);
400402
PyCodeObject *code = (PyCodeObject *)getitem->func_code;
401403
assert(code->co_argcount == 2);
402404
DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), BINARY_SUBSCR);

0 commit comments

Comments
 (0)