8000 bpo-42927: Inline cache for attributes defined with '__slots__' (#24216) · python/cpython@5c5a938 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5c5a938

Browse files
authored
bpo-42927: Inline cache for attributes defined with '__slots__' (#24216)
1 parent ba7a99d commit 5c5a938

File tree

2 files changed

+118
-64
lines changed

2 files changed

+118
-64
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The inline cache for ``LOAD_ATTR`` now also optimizes access to attributes defined by ``__slots__``.
2+
This makes reading such attribute up to 30% faster.

Python/ceval.c

Lines changed: 116 additions & 64 deletions
Original file line numberDiff line numberDiff line change
2929
#include "opcode.h"
3030
#include "pydtrace.h"
3131
#include "setobject.h"
32+
#include "structmember.h" // struct PyMemberDef, T_OFFSET_EX
3233

3334
#include <ctype.h>
3435

@@ -3169,114 +3170,165 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
31693170
if (co_opcache != NULL && PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG))
31703171
{
31713172
if (co_opcache->optimized > 0) {
3172-
/* Fast path -- cache hit makes LOAD_ATTR ~30% faster */
3173+
// Fast path -- cache hit makes LOAD_ATTR ~30% faster.
31733174
la = &co_opcache->u.la;
31743175
if (la->type == type && la->tp_version_tag == type->tp_version_tag)
31753176
{
3176-
assert(type->tp_dict != NULL);
3177-
assert(type->tp_dictoffset > 0);
3178-
3179-
dictptr = (PyObject **) ((char *)owner + type->tp_dictoffset);
3180-
< 8000 span class=pl-s1>dict = *dictptr;
3181-
if (dict != NULL && PyDict_CheckExact(dict)) {
3182-
Py_ssize_t hint = la->hint;
3183-
Py_INCREF(dict);
3184-
res = NULL;
3185-
la->hint = _PyDict_GetItemHint((PyDictObject*)dict, name, hint, &res);
3186-
3177+
// Hint >= 0 is a dict index; hint == -1 is a dict miss.
3178+
// Hint < -1 is an inverted slot offset: offset is strictly > 0,
3179+
// so ~offset is strictly < -1 (assuming 2's complement).
3180+
if (la->hint < -1) {
3181+
// Even faster path -- slot hint.
3182+
Py_ssize_t offset = ~la->hint;
3183+
// fprintf(stderr, "Using hint for offset %zd\n", offset);
3184+
char *addr = (char *)owner + offset;
3185+
res = *(PyObject **)addr;
31873186
if (res != NULL) {
3188-
if (la->hint == hint && hint >= 0) {
3189-
/* Our hint has helped -- cache hit. */
3190-
OPCACHE_STAT_ATTR_HIT();
3191-
} else {
3192-
/* The hint we provided didn't work.
3193-
Maybe next time? */
3194-
OPCACHE_MAYBE_DEOPT_LOAD_ATTR();
3195-
}
3196-
31973187
Py_INCREF(res);
31983188
SET_TOP(res);
31993189
Py_DECREF(owner);
3200-
Py_DECREF(dict);
32013190
DISPATCH();
3191+
}
3192+
// Else slot is NULL. Fall through to slow path to raise AttributeError(name).
3193+
// Don't DEOPT, since the slot is still there.
3194+
} else {
3195+
// Fast path for dict.
3196+
assert(type->tp_dict != NULL);
3197+
assert(type->tp_dictoffset > 0);
3198+
3199+
dictptr = (PyObject **) ((char *)owner + type->tp_dictoffset);
3200+
dict = *dictptr;
3201+
if (dict != NULL && PyDict_CheckExact(dict)) {
3202+
Py_ssize_t hint = la->hint;
3203+
Py_INCREF(dict);
3204+
res = NULL;
3205+
la->hint = _PyDict_GetItemHint((PyDictObject*)dict, name, hint, &res);
3206+
3207+
if (res != NULL) {
3208+
if (la->hint == hint && hint >= 0) {
3209+
// Our hint has helped -- cache hit.
3210+
OPCACHE_STAT_ATTR_HIT();
3211+
} else {
3212+
// The hint we provided didn't work.
3213+
// Maybe next time?
3214+
OPCACHE_MAYBE_DEOPT_LOAD_ATTR();
3215+
}
3216+
3217+
Py_INCREF(res);
3218+
SET_TOP(res);
3219+
Py_DECREF(owner);
3220+
Py_DECREF(dict);
3221+
DISPATCH();
3222+
} else {
3223+
// This attribute can be missing sometimes;
3224+
// we don't want to optimize this lookup.
3225+
OPCACHE_DEOPT_LOAD_ATTR();
3226+
Py_DECREF(dict);
3227+
}
32023228
} else {
3203-
// This attribute can be missing sometimes -- we
3204-
// don't want to optimize this lookup.
3229+
// There is no dict, or __dict__ doesn't satisfy PyDict_CheckExact.
32053230
OPCACHE_DEOPT_LOAD_ATTR();
3206-
Py_DECREF(dict);
32073231
}
3208-
} else {
3209-
// There is no dict, or __dict__ doesn't satisfy PyDict_CheckExact
3210-
OPCACHE_DEOPT_LOAD_ATTR();
32113232
}
32123233
} else {
32133234
// The type of the object has either been updated,
32143235
// or is different. Maybe it will stabilize?
32153236
OPCACHE_MAYBE_DEOPT_LOAD_ATTR();
32163237
}
3217-
32183238
OPCACHE_STAT_ATTR_MISS();
32193239
}
32203240

3221-
if (co_opcache != NULL && /* co_opcache can be NULL after a DEOPT() call. */
3241+
if (co_opcache != NULL && // co_opcache can be NULL after a DEOPT() call.
32223242
type->tp_getattro == PyObject_GenericGetAttr)
32233243
{
3224-
Py_ssize_t ret;
3225-
3226-
if (type->tp_dictoffset > 0) {
3227-
if (type->tp_dict == NULL) {
3228-
if (PyType_Ready(type) < 0) {
3229-
Py_DECREF(owner);
3230-
SET_TOP(NULL);
3231-
goto error;
3232-
}
3244+
if (type->tp_dict == NULL) {
3245+
if (PyType_Ready(type) < 0) {
3246+
Py_DECREF(owner);
3247+
SET_TOP(NULL);
3248+
goto error;
32333249
}
3234-
if (_PyType_Lookup(type, name) == NULL) {
3235-
dictptr = (PyObject **) ((char *)owner + type->tp_dictoffset);
3236-
dict = *dictptr;
3250+
}
3251+
PyObject *descr = _PyType_Lookup(type, name);
3252+
if (descr != NULL) {
3253+
// We found an attribute with a data-like descriptor.
3254+
PyTypeObject *dtype = Py_TYPE(descr);
3255+
if (dtype == &PyMemberDescr_Type) { // It's a slot
3256+
PyMemberDescrObject *member = (PyMemberDescrObject *)descr;
3257+
struct PyMemberDef *dmem = member->d_member;
3258+
if (dmem->type == T_OBJECT_EX) {
3259+
Py_ssize_t offset = dmem->offset;
3260+
assert(offset > 0); // 0 would be confused with dict hint == -1 (miss).
3261+
3262+
if (co_opcache->optimized == 0) {
3263+
// First time we optimize this opcode.
3264+
OPCACHE_STAT_ATTR_OPT();
3265+
co_opcache->optimized = OPCODE_CACHE_MAX_TRIES;
3266+
// fprintf(stderr, "Setting hint for %s, offset %zd\n", dmem->name, offset);
3267+
}
32373268

3238-
if (dict != NULL && PyDict_CheckExact(dict)) {
3239-
Py_INCREF(dict);
3240-
res = NULL;
3241-
ret = _PyDict_GetItemHint((PyDictObject*)dict, name, -1, &res);
3269+
la = &co_opcache->u.la;
3270+
la->type = type;
3271+
la->tp_version_tag = type->tp_version_tag;
3272+
la->hint = ~offset;
3273+
3274+
char *addr = (char *)owner + offset;
3275+
res = *(PyObject **)addr;
32423276
if (res != NULL) {
32433277
Py_INCREF(res);
3244-
Py_DECREF(dict);
32453278
Py_DECREF(owner);
32463279
SET_TOP(res);
32473280

3248-
if (co_opcache->optimized == 0) {
3249-
// First time we optimize this opcode. */
3250-
OPCACHE_STAT_ATTR_OPT();
3251-
co_opcache->optimized = OPCODE_CACHE_MAX_TRIES;
3252-
}
3253-
3254-
la = &co_opcache->u.la;
3255-
la->type = type;
3256-
la->tp_version_tag = type->tp_version_tag;
3257-
la->hint = ret;
3258-
32593281
DISPATCH();
32603282
}
3283+
// Else slot is NULL. Fall through to slow path to raise AttributeError(name).
3284+
}
3285+
// Else it's a slot of a different type. We don't handle those.
3286+
}
3287+
// Else it's some other kind of descriptor that we don't handle.
3288+
OPCACHE_DEOPT_LOAD_ATTR();
3289+
} else if (type->tp_dictoffset > 0) {
3290+
// We found an instance with a __dict__.
3291+
dictptr = (PyObject **) ((char *)owner + type->tp_dictoffset);
3292+
dict = *dictptr;
3293+
3294+
if (dict != NULL && PyDict_CheckExact(dict)) {
3295+
Py_INCREF(dict);
3296+
res = NULL;
3297+
Py_ssize_t hint = _PyDict_GetItemHint((PyDictObject*)dict, name, -1, &res);
3298+
if (re F987 s != NULL) {
3299+
Py_INCREF(res);
32613300
Py_DECREF(dict);
3262-
} else {
3263-
// There is no dict, or __dict__ doesn't satisfy PyDict_CheckExact
3264-
OPCACHE_DEOPT_LOAD_ATTR();
3301+
Py_DECREF(owner);
3302+
SET_TOP(res);
3303+
3304+
if (co_opcache->optimized == 0) {
3305+
// First time we optimize this opcode.
3306+
OPCACHE_STAT_ATTR_OPT();
3307+
co_opcache->optimized = OPCODE_CACHE_MAX_TRIES;
3308+
}
3309+
3310+
la = &co_opcache->u.la;
3311+
la->type = type;
3312+
la->tp_version_tag = type->tp_version_tag;
3313+
la->hint = hint;
3314+
3315+
DISPATCH();
32653316
}
3317+
Py_DECREF(dict);
32663318
} else {
3267-
// We failed to find an attribute without a data-like descriptor
3319+
// There is no dict, or __dict__ doesn't satisfy PyDict_CheckExact.
32683320
OPCACHE_DEOPT_LOAD_ATTR();
32693321
}
32703322
} else {
3271-
// The object's class does not have a tp_dictoffset we can use
3323+
// The object's class does not have a tp_dictoffset we can use.
32723324
OPCACHE_DEOPT_LOAD_ATTR();
32733325
}
32743326
} else if (type->tp_getattro != PyObject_GenericGetAttr) {
32753327
OPCACHE_DEOPT_LOAD_ATTR();
32763328
}
32773329
}
32783330

3279-
/* slow path */
3331+
// Slow path.
32803332
res = PyObject_GetAttr(owner, name);
32813333
Py_DECREF(owner);
32823334
SET_TOP(res);

0 commit comments

Comments
 (0)
0