Repository navigation
gh-112354: Add executor for less-taken branch #112902
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 1 commit
36feeb1
d12533b
f21f2d8
8463965
6403752
329dead
f1998c0
b0944e6
26b5f89
649581c
835bf13
256b156
75c7c32
a94c7f1
747a3f0
682cf5a
359c6fc
ca6ed3a
e2a26b5
38c7aab
0f64231
83297df
d065a94
0f71a03
075ab91
c54daef
934a115
52c49eb
8f5e623
10b98f1
1450ca6
dcde4d3
15df63f
c786418
ee0734b
655a841
32e36fa
f5b317a
4804a3c
46c7d26
b991279
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -752,7 +752,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | |
| goto exit_unwind; | ||
| } | ||
|
|
||
| // Jump here from ENTER_EXECUTOR, and code under the deoptimize label | ||
| enter_tier_one: | ||
| next_instr = frame->instr_ptr; | ||
|
|
||
| resume_frame: | ||
| stack_pointer = _PyFrame_GetStackPointer(frame); | ||
|
|
||
|
|
@@ -1063,50 +1066,20 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | |
|
|
||
| // Jump here from DEOPT_IF() | ||
| deoptimize: | ||
| // On DEOPT_IF we just repeat the last instruction. | ||
| // On DEOPT_IF we must repeat the last instruction. | ||
|
gvanrossum marked this conversation as resolved.
Outdated
|
||
| // This presumes nothing was popped from the stack (nor pushed). | ||
| // There are some other things to take care of first, though. | ||
| frame->instr_ptr = next_uop[-1].target + _PyCode_CODE(_PyFrame_GetCode(frame)); | ||
| DPRINTF(2, "DEOPT: [UOp %d (%s), oparg %d, operand %" PRIu64 ", target %d @ %d -> %s]\n", | ||
| uopcode, _PyUOpName(uopcode), next_uop[-1].oparg, next_uop[-1].operand, next_uop[-1].target, | ||
| (int)(next_uop - current_executor->trace - 1), | ||
| _PyOpcode_OpName[frame->instr_ptr->op.code]); | ||
| OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); | ||
| UOP_STAT_INC(uopcode, miss); | ||
| _Py_CODEUNIT *src, *dest; | ||
| src = dest = frame->instr_ptr; | ||
| opcode = src->op.code; | ||
| oparg = src->op.arg; | ||
| while (opcode == EXTENDED_ARG) { | ||
| src++; | ||
| opcode = src->op.code; | ||
| oparg = (oparg << 8) | src->op.arg; | ||
| } | ||
| if (opcode == ENTER_EXECUTOR) { | ||
| // frame->instr_ptr --> dest --> EXTENDED_ARG | ||
| // src --> ENTER_EXECUTOR | ||
| // Avoid recursing into the same executor over and over | ||
| next_instr = src; | ||
| PyCodeObject *code = _PyFrame_GetCode(frame); | ||
| _PyExecutorObject *executor = (_PyExecutorObject *)code->co_executors->executors[oparg & 0xff]; | ||
| if (executor == (_PyExecutorObject *)current_executor) { | ||
| opcode = _PyOpcode_Deopt[executor->vm_data.opcode]; | ||
| DPRINTF(1, "Avoiding ENTER_EXECUTOR %d/%d in favor of underlying base opcode %s %d\n", | ||
| oparg, oparg & 0xff, _PyOpcode_OpName[opcode], (oparg & 0xffffff00) | executor->vm_data.oparg); | ||
| DPRINTF(1, | ||
| " for %s (%s:%d) at byte offset %d\n", | ||
| PyUnicode_AsUTF8(code->co_qualname), | ||
| PyUnicode_AsUTF8(code->co_filename), | ||
| code->co_firstlineno, | ||
| 2 * (int)(next_instr - _PyCode_CODE(_PyFrame_GetCode(frame)))); | ||
| Py_DECREF(current_executor); | ||
| oparg = (oparg & 0xffffff00) | executor->vm_data.oparg; | ||
| PRE_DISPATCH_GOTO(); | ||
| DISPATCH_GOTO(); | ||
| } | ||
| } | ||
| frame->return_offset = 0; // Don't leave this random | ||
| _PyFrame_SetStackPointer(frame, stack_pointer); | ||
| // Increment side exit counter for this uop | ||
|
|
||
| // Check if there is a side-exit executor here already. | ||
| int pc = next_uop - 1 - current_executor->trace; | ||
| _PyExecutorObject **pexecutor = current_executor->executors + pc; | ||
| if (*pexecutor != NULL) { | ||
|
|
@@ -1121,14 +1094,30 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | |
| Py_INCREF(current_executor); | ||
| goto enter_tier_two; | ||
| } | ||
|
|
||
| // Increment and check side exit counter. | ||
| uint16_t *pcounter = current_executor->counters + pc; | ||
| *pcounter += 1; | ||
| if (*pcounter == 32 && // TODO: use resume_threshold | ||
| tstate->interp->optimizer != &_PyOptimizer_Default && | ||
| (opcode == POP_JUMP_IF_FALSE || | ||
| opcode == POP_JUMP_IF_TRUE || | ||
| opcode == POP_JUMP_IF_NONE || | ||
| opcode == POP_JUMP_IF_NOT_NONE)) | ||
| if (*pcounter != 32 || // TODO: use resume_threshold | ||
| tstate->interp->optimizer == &_PyOptimizer_Default) | ||
| { | ||
| goto enter_tier_one; | ||
| } | ||
|
|
||
| // Decode instruction to look past EXTENDED_ARG. | ||
| _Py_CODEUNIT *src, *dest; | ||
| src = dest = frame->instr_ptr; | ||
| opcode = src->op.code; | ||
| if (opcode == EXTENDED_ARG) { | ||
| src++; | ||
| opcode = src->op.code; | ||
| } | ||
|
|
||
| // For selected opcodes build a new executor and enter it now. | ||
|
Member
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. Why "selected opcodes", why not everywhere?
Member
Author
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. In an earlier version that somehow didn't work. Right now the check whether the new trace isn't going to immediately deopt again relies on these opcodes. I figured once we have the side exit machinery working we could gradually increase the scope to other deoptimizations. Also, not all deoptimizations are worthy of the effort (e.g. the PEP 523 test).
Member
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. No special cases, please, it just make the code more complicated and slower.
Member
Author
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. There are several reasons. First, as I explain below, for bytecodes other than branches, I can't promise an exact check for whether the newly created sub-executor doesn't just repeat the same deoptimizing uop that triggered its creation (in which case the sub-executor would always deopt immediately if it is entered at all). Second, for most bytecodes other than branches, deoptimization paths are relatively rare (IIRC this is apparent from the pystats data -- with the exception of some For branches, we expect many cases where the "common" path is not much more common than the "uncommon" path (e.g. 60/40 or 70/30). Now, it might make sense have a different special case here, where if e.g. I propose this PR as a starting point for futher iterations, not as the ultimate design for side-exits. Let's discuss this Monday. |
||
| if (opcode == POP_JUMP_IF_FALSE || | ||
| opcode == POP_JUMP_IF_TRUE || | ||
| opcode == POP_JUMP_IF_NONE || | ||
| opcode == POP_JUMP_IF_NOT_NONE) | ||
| { | ||
| DPRINTF(2, "--> %s @ %d in %p has %d side exits\n", | ||
| _PyUOpName(uopcode), pc, current_executor, (int)(*pcounter)); | ||
|
|
@@ -1138,7 +1127,11 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | |
| if (optimized < 0) { | ||
| goto error_tier_two; | ||
| } | ||
| if (optimized) { | ||
| if (!optimized) { | ||
| DPRINTF(2, "--> Failed to optimize %s @ %d in %p\n", | ||
| _PyUOpName(uopcode), pc, current_executor); | ||
| } | ||
| else { | ||
| DPRINTF(1, "--> Optimized %s @ %d in %p\n", | ||
| _PyUOpName(uopcode), pc, current_executor); | ||
| DPRINTF(1, " T1: %s\n", _PyOpcode_OpName[src->op.code]); | ||
|
|
@@ -1150,28 +1143,18 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | |
| 2 * (int)(frame->instr_ptr - _PyCode_CODE(_PyFrame_GetCode(frame)))); | ||
| Py_DECREF(current_executor); | ||
| current_executor = (_PyUOpExecutorObject *)*pexecutor; | ||
|
gvanrossum marked this conversation as resolved.
Outdated
|
||
| // TODO: Check at least two uops: _IS_NONE, _POP_JUMP_IF_TRUE/FALSE. | ||
| if (current_executor->trace[0].opcode != uopcode) { | ||
| Py_INCREF(current_executor); | ||
| goto enter_tier_two; | ||
| goto enter_tier_two; // Yes! | ||
| } | ||
| // This is guaranteed to deopt again; forget about it | ||
| DPRINTF(2, "It's not an improvement -- discarding trace\n"); | ||
| DPRINTF(2, "Alas, it's the same uop again -- discarding trace\n"); | ||
| *pexecutor = NULL; | ||
| Py_DECREF(current_executor); | ||
| next_instr = frame->instr_ptr; | ||
| goto resume_frame; | ||
| } | ||
| else { | ||
| DPRINTF(2, "--> Failed to optimize %s @ %d in %p\n", | ||
| _PyUOpName(uopcode), pc, current_executor); | ||
| } | ||
| } | ||
| Py_DECREF(current_executor); | ||
| // Fall through | ||
| // Jump here from ENTER_EXECUTOR | ||
| enter_tier_one: | ||
| next_instr = frame->instr_ptr; | ||
| goto resume_frame; | ||
| goto enter_tier_one; | ||
|
|
||
| // Jump here from _EXIT_TRACE | ||
| exit_trace: | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.