-
-
Notifications
You must be signed in to change notification settings - Fork 31.8k
bpo-46939: Specialize calls to Python classes #31707
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
43112e0
09a7180
d74d294
0583f6b
bb78e9c
b570e92
efad70f
4d6a06b
5b526af
de3a406
30a0659
db09eef
0d7f59e
28f9dbb
d11362f
c04f5da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Calls to Python classes are now specialized. Creating objects from Python | ||
classes should now be faster. Patch by Ken Jin. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1463,6 +1463,13 @@ eval_frame_handle_pending(PyThreadState *tstate) | |
STAT_INC(LOAD_##attr_or_method, hit); \ | ||
Py_INCREF(res); | ||
|
||
#define CALL_PY_FRAME_PASS_SELF() \ | ||
if (call_shape.init_pass_self) { \ | ||
assert(frame->self == NULL); \ | ||
frame->self = Py_NewRef(frame->localsplus[0]); \ | ||
call_shape.init_pass_self = false; \ | ||
} | ||
|
||
#define TRACE_FUNCTION_EXIT() \ | ||
if (cframe.use_tracing) { \ | ||
if (trace_function_exit(tstate, frame, retval)) { \ | ||
|
@@ -1588,6 +1595,11 @@ pop_frame(PyThreadState *tstate, _PyInterpreterFrame *frame) | |
*/ | ||
typedef struct { | ||
PyObject *kwnames; | ||
/* __init__ is special because while it returns None, we need to return self | ||
This tells CALL to pass the current self to the new frame (the __init__ frame). | ||
Where it is eventually consumed by RETURN_VALUE. | ||
*/ | ||
bool init_pass_self; | ||
} CallShape; | ||
|
||
static inline bool | ||
|
@@ -1619,6 +1631,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | |
_PyCFrame cframe; | ||
CallShape call_shape; | ||
call_shape.kwnames = NULL; // Borrowed reference. Reset by CALL instructions. | ||
call_shape.init_pass_self = 0; | ||
|
||
/* WARNING: Because the _PyCFrame lives on the C stack, | ||
* but can be accessed from a heap allocated object (tstate) | ||
|
@@ -2391,6 +2404,18 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | |
|
||
TARGET(RETURN_VALUE) { | ||
PyObject *retval = POP(); | ||
if (frame->self != NULL) { | ||
if (Py_IsNone(retval)) { | ||
Py_SETREF(retval, frame->self); | ||
frame->self = NULL; | ||
} | ||
/* We need this to continue raising errors when bad-practice | ||
__init__s return their non-None values. This is later | ||
caught by the interpreter. */ | ||
else { | ||
Py_CLEAR(frame->self); | ||
} | ||
} | ||
assert(EMPTY()); | ||
frame->f_state = FRAME_RETURNED; | ||
_PyFrame_SetStackPointer(frame, stack_pointer); | ||
|
@@ -4611,6 +4636,37 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | |
DISPATCH(); | ||
} | ||
|
||
TARGET(PRECALL_PY_CLASS) { | ||
_PyPrecallCache *cache = (_PyPrecallCache *)next_instr; | ||
int is_method = (PEEK(oparg + 2) != NULL); | ||
DEOPT_IF(is_method, PRECALL); | ||
PyObject *cls = PEEK(oparg + 1); | ||
DEOPT_IF(!PyType_Check(cls), PRECALL); | ||
PyTypeObject *cls_t = (PyTypeObject *)cls; | ||
DEOPT_IF(cls_t->tp_version_tag != read_u32(cache->type_version), PRECALL); | ||
assert(cls_t->tp_flags & Py_TPFLAGS_HEAPTYPE); | ||
PyObject *init = ((PyHeapTypeObject *)cls_t)->_spec_cache.init; | ||
assert(PyFunction_Check(init)); | ||
DEOPT_IF(cls_t->tp_new != PyBaseObject_Type.tp_new, PRECALL); | ||
Fidget-Spinner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
STAT_INC(PRECALL, hit); | ||
|
||
PyObject *self = _PyObject_New_Vector(cls_t, &PEEK(oparg), | ||
(Py_ssize_t)oparg, call_shape.kwnames); | ||
if (self == NULL) { | ||
goto error; | ||
} | ||
Py_INCREF(init); | ||
PEEK(oparg+1) = self; | ||
PEEK(oparg+2) = init; | ||
Py_DECREF(cls); | ||
|
||
/* For use in RETURN_VALUE later */ | ||
assert(call_shape.init_pass_self == false); | ||
call_shape.init_pass_self = true; | ||
JUMPBY(INLINE_CACHE_ENTRIES_PRECALL); | ||
DISPATCH(); | ||
} | ||
|
||
TARGET(KW_NAMES) { | ||
assert(call_shape.kwnames == NULL); | ||
assert(oparg < PyTuple_GET_SIZE(consts)); | ||
|
@@ -4646,6 +4702,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | |
frame->f_lasti += INLINE_CACHE_ENTRIES_CALL; | ||
new_frame->previous = frame; | ||
cframe.current_frame = frame = new_frame; | ||
CALL_PY_FRAME_PASS_SELF(); | ||
CALL_STAT_INC(inlined_py_calls); | ||
goto start_frame; | ||
} | ||
|
@@ -4751,6 +4808,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | |
frame->f_lasti += INLINE_CACHE_ENTRIES_CALL; | ||
new_frame->previous = frame; | ||
frame = cframe.current_frame = new_frame; | ||
CALL_PY_FRAME_PASS_SELF(); | ||
goto start_frame; | ||
} | ||
|
||
|
@@ -4791,6 +4849,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | |
frame->f_lasti += INLINE_CACHE_ENTRIES_CALL; | ||
new_frame->previous = frame; | ||
frame = cframe.current_frame = new_frame; | ||
CALL_PY_FRAME_PASS_SELF(); | ||
goto start_frame; | ||
} | ||
|
||
|
@@ -5557,6 +5616,7 @@ MISS_WITH_INLINE_CACHE(STORE_SUBSCR) | |
|
||
error: | ||
call_shape.kwnames = NULL; | ||
call_shape.init_pass_self = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note to reviewers: We don't set class A:
def __init__(self):
try:
A.a # Kaboom!
except AttributeError:
pass
for _ in range(10):
print(A()) |
||
/* Double-check exception status. */ | ||
#ifdef NDEBUG | ||
if (!_PyErr_Occurred(tstate)) { | ||
|
@@ -5598,6 +5658,7 @@ MISS_WITH_INLINE_CACHE(STORE_SUBSCR) | |
assert(STACK_LEVEL() == 0); | ||
_PyFrame_SetStackPointer(frame, stack_pointer); | ||
frame->f_state = FRAME_RAISED; | ||
Py_CLEAR(frame->self); | ||
TRACE_FUNCTION_UNWIND(); | ||
DTRACE_FUNCTION_EXIT(); | ||
goto exit_unwind; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note to reviewers: tied to frame state instead of some cache/call_shape so that subsequent nested calls don't destroy
self
(and we can identify which frame theself
belongs to). Consider the following code: