Skip to content
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

GH-93354: Use exponential backoff to avoid excessive specialization attempts. #93355

Merged
merged 2 commits into from
May 31, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions Include/internal/pycore_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,10 @@ extern void _PyLineTable_InitAddressRange(
extern int _PyLineTable_NextAddressRange(PyCodeAddressRange *range);
extern int _PyLineTable_PreviousAddressRange(PyCodeAddressRange *range);


#define ADAPTIVE_CACHE_BACKOFF 64
/* With a 16 counter, we have 12 bits for the counter value, and 4 bits for the backoff */
sweeneyde marked this conversation as resolved.
Show resolved Hide resolved
#define ADAPTIVE_BACKOFF_BITS 4
/* The initial counter value is backoff**2-1, so a backoff of 5 gives a value of 31 */
sweeneyde marked this conversation as resolved.
Show resolved Hide resolved
#define ADAPTIVE_BACKOFF_START 5

/* Specialization functions */

Expand Down Expand Up @@ -423,6 +425,32 @@ write_location_entry_start(uint8_t *ptr, int code, int length)
}


static inline uint16_t
adaptive_counter_bits(int value, int backoff) {
return (value << ADAPTIVE_BACKOFF_BITS) |
(backoff & ((1<<ADAPTIVE_BACKOFF_BITS)-1));
}

#define MAX_BACKOFF_VALUE (16 - ADAPTIVE_BACKOFF_BITS)

static inline uint16_t
adaptive_counter_start(void) {
unsigned int value = (1 << ADAPTIVE_BACKOFF_START) - 1;
return adaptive_counter_bits(value, ADAPTIVE_BACKOFF_START);
}

static inline uint16_t
adaptive_counter_backoff(uint16_t counter) {
unsigned int backoff = counter & ((1<<ADAPTIVE_BACKOFF_BITS)-1);
backoff++;
if (backoff > MAX_BACKOFF_VALUE) {
backoff = MAX_BACKOFF_VALUE;
}
unsigned int value = (1 << backoff) - 1;
return adaptive_counter_bits(value, backoff);
}


#ifdef __cplusplus
}
#endif
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Use exponential backoff for specialization counters in the interpreter. Can
reduce the number of failed specializations significantly and avoid slowdown
for those parts of a program that are not suitable for specialization.
46 changes: 25 additions & 21 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -1561,7 +1561,11 @@ eval_frame_handle_pending(PyThreadState *tstate)
dtrace_function_entry(frame); \
}

#define ADAPTIVE_COUNTER_IS_ZERO(cache) \
(cache)->counter < (1<<ADAPTIVE_BACKOFF_BITS)

#define DECREMENT_ADAPTIVE_COUNTER(cache) \
(cache)->counter -= (1<<ADAPTIVE_BACKOFF_BITS)

static int
trace_function_entry(PyThreadState *tstate, _PyInterpreterFrame *frame)
Expand Down Expand Up @@ -2156,7 +2160,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int

TARGET(BINARY_SUBSCR_ADAPTIVE) {
_PyBinarySubscrCache *cache = (_PyBinarySubscrCache *)next_instr;
if (cache->counter == 0) {
if (ADAPTIVE_COUNTER_IS_ZERO(cache)) {
PyObject *sub = TOP();
PyObject *container = SECOND();
next_instr--;
Expand All @@ -2167,7 +2171,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
}
else {
STAT_INC(BINARY_SUBSCR, deferred);
cache->counter--;
DECREMENT_ADAPTIVE_COUNTER(cache);
JUMP_TO_INSTRUCTION(BINARY_SUBSCR);
}
}
Expand Down Expand Up @@ -2321,7 +2325,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int

TARGET(STORE_SUBSCR_ADAPTIVE) {
_PyStoreSubscrCache *cache = (_PyStoreSubscrCache *)next_instr;
if (cache->counter == 0) {
if (ADAPTIVE_COUNTER_IS_ZERO(cache)) {
PyObject *sub = TOP();
PyObject *container = SECOND();
next_instr--;
Expand All @@ -2332,7 +2336,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
}
else {
STAT_INC(STORE_SUBSCR, deferred);
cache->counter--;
DECREMENT_ADAPTIVE_COUNTER(cache);
JUMP_TO_INSTRUCTION(STORE_SUBSCR);
}
}
Expand Down Expand Up @@ -2815,15 +2819,15 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
TARGET(UNPACK_SEQUENCE_ADAPTIVE) {
assert(cframe.use_tracing == 0);
_PyUnpackSequenceCache *cache = (_PyUnpackSequenceCache *)next_instr;
if (cache->counter == 0) {
if (ADAPTIVE_COUNTER_IS_ZERO(cache)) {
PyObject *seq = TOP();
next_instr--;
_Py_Specialize_UnpackSequence(seq, next_instr, oparg);
NOTRACE_DISPATCH_SAME_OPARG();
}
else {
STAT_INC(UNPACK_SEQUENCE, deferred);
cache->counter--;
DECREMENT_ADAPTIVE_COUNTER(cache);
JUMP_TO_INSTRUCTION(UNPACK_SEQUENCE);
}
}
Expand Down Expand Up @@ -3056,7 +3060,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
TARGET(LOAD_GLOBAL_ADAPTIVE) {
assert(cframe.use_tracing == 0);
_PyLoadGlobalCache *cache = (_PyLoadGlobalCache *)next_instr;
if (cache->counter == 0) {
if (ADAPTIVE_COUNTER_IS_ZERO(cache)) {
PyObject *name = GETITEM(names, oparg>>1);
next_instr--;
if (_Py_Specialize_LoadGlobal(GLOBALS(), BUILTINS(), next_instr, name) < 0) {
Expand All @@ -3066,7 +3070,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
}
else {
STAT_INC(LOAD_GLOBAL, deferred);
cache->counter--;
DECREMENT_ADAPTIVE_COUNTER(cache);
JUMP_TO_INSTRUCTION(LOAD_GLOBAL);
}
}
Expand Down Expand Up @@ -3480,7 +3484,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
TARGET(LOAD_ATTR_ADAPTIVE) {
assert(cframe.use_tracing == 0);
_PyAttrCache *cache = (_PyAttrCache *)next_instr;
if (cache->counter == 0) {
if (ADAPTIVE_COUNTER_IS_ZERO(cache)) {
PyObject *owner = TOP();
PyObject *name = GETITEM(names, oparg);
next_instr--;
Expand All @@ -3491,7 +3495,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
}
else {
STAT_INC(LOAD_ATTR, deferred);
cache->counter--;
DECREMENT_ADAPTIVE_COUNTER(cache);
JUMP_TO_INSTRUCTION(LOAD_ATTR);
}
}
Expand Down Expand Up @@ -3589,7 +3593,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
TARGET(STORE_ATTR_ADAPTIVE) {
assert(cframe.use_tracing == 0);
_PyAttrCache *cache = (_PyAttrCache *)next_instr;
if (cache->counter == 0) {
if (ADAPTIVE_COUNTER_IS_ZERO(cache)) {
PyObject *owner = TOP();
PyObject *name = GETITEM(names, oparg);
next_instr--;
Expand All @@ -3600,7 +3604,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
}
else {
STAT_INC(STORE_ATTR, deferred);
cache->counter--;
DECREMENT_ADAPTIVE_COUNTER(cache);
JUMP_TO_INSTRUCTION(STORE_ATTR);
}
}
Expand Down Expand Up @@ -3719,7 +3723,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
TARGET(COMPARE_OP_ADAPTIVE) {
assert(cframe.use_tracing == 0);
_PyCompareOpCache *cache = (_PyCompareOpCache *)next_instr;
if (cache->counter == 0) {
if (ADAPTIVE_COUNTER_IS_ZERO(cache)) {
PyObject *right = TOP();
PyObject *left = SECOND();
next_instr--;
Expand All @@ -3728,7 +3732,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
}
else {
STAT_INC(COMPARE_OP, deferred);
cache->counter--;
DECREMENT_ADAPTIVE_COUNTER(cache);
JUMP_TO_INSTRUCTION(COMPARE_OP);
}
}
Expand Down Expand Up @@ -4526,7 +4530,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
TARGET(LOAD_METHOD_ADAPTIVE) {
assert(cframe.use_tracing == 0);
_PyLoadMethodCache *cache = (_PyLoadMethodCache *)next_instr;
if (cache->counter == 0) {
if (ADAPTIVE_COUNTER_IS_ZERO(cache)) {
PyObject *owner = TOP();
PyObject *name = GETITEM(names, oparg);
next_instr--;
Expand All @@ -4537,7 +4541,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
}
else {
STAT_INC(LOAD_METHOD, deferred);
cache->counter--;
DECREMENT_ADAPTIVE_COUNTER(cache);
JUMP_TO_INSTRUCTION(LOAD_METHOD);
}
}
Expand Down Expand Up @@ -4775,7 +4779,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int

TARGET(CALL_ADAPTIVE) {
_PyCallCache *cache = (_PyCallCache *)next_instr;
if (cache->counter == 0) {
if (ADAPTIVE_COUNTER_IS_ZERO(cache)) {
next_instr--;
int is_meth = is_method(stack_pointer, oparg);
int nargs = oparg + is_meth;
Expand All @@ -4789,7 +4793,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
}
else {
STAT_INC(CALL, deferred);
cache->counter--;
DECREMENT_ADAPTIVE_COUNTER(cache);
goto call_function;
}
}
Expand Down Expand Up @@ -5521,7 +5525,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
TARGET(BINARY_OP_ADAPTIVE) {
assert(cframe.use_tracing == 0);
_PyBinaryOpCache *cache = (_PyBinaryOpCache *)next_instr;
if (cache->counter == 0) {
if (ADAPTIVE_COUNTER_IS_ZERO(cache)) {
PyObject *lhs = SECOND();
PyObject *rhs = TOP();
next_instr--;
Expand All @@ -5530,7 +5534,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
}
else {
STAT_INC(BINARY_OP, deferred);
cache->counter--;
DECREMENT_ADAPTIVE_COUNTER(cache);
JUMP_TO_INSTRUCTION(BINARY_OP);
}
}
Expand Down Expand Up @@ -5658,7 +5662,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
assert(adaptive_opcode);
_Py_SET_OPCODE(next_instr[-1], adaptive_opcode);
STAT_INC(opcode, deopt);
*counter = ADAPTIVE_CACHE_BACKOFF;
*counter = adaptive_counter_start();
}
next_instr--;
DISPATCH_GOTO();
Expand Down
Loading