Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
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
30 changes: 16 additions & 14 deletions Include/internal/pycore_backoff.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,20 @@ extern "C" {
Another use is for the Tier 2 optimizer to decide when to create
a new Tier 2 trace (executor). Again, exponential backoff is used.

The 16-bit counter is structured as a 12-bit unsigned 'value'
and a 4-bit 'backoff' field. When resetting the counter, the
The 16-bit counter is structured as a 13-bit unsigned 'value'
and a 3-bit 'backoff' field. When resetting the counter, the
backoff field is incremented (until it reaches a limit) and the
value is set to a bit mask representing the value 2**backoff - 1.
The maximum backoff is 12 (the number of bits in the value).
value is set to a bit mask representing the value 2**(2*backoff+1) - 1.
The maximum backoff is 6, since 7 is an UNREACHABLE_BACKOFF.

There is an exceptional value which must not be updated, 0xFFFF.
*/

#define BACKOFF_BITS 4
#define MAX_BACKOFF 12
#define UNREACHABLE_BACKOFF 15
#define BACKOFF_BITS 3
#define BACKOFF_MASK 7
#define MAX_BACKOFF 6
#define UNREACHABLE_BACKOFF 7
#define MAX_VALUE 0x1FFF

static inline bool
is_unreachable_backoff_counter(_Py_BackoffCounter counter)
Expand All @@ -44,8 +46,8 @@ is_unreachable_backoff_counter(_Py_BackoffCounter counter)
static inline _Py_BackoffCounter
make_backoff_counter(uint16_t value, uint16_t backoff)
{
assert(backoff <= 15);
assert(value <= 0xFFF);
assert(backoff <= UNREACHABLE_BACKOFF);
assert(value <= MAX_VALUE);
_Py_BackoffCounter result;
result.value_and_backoff = (value << BACKOFF_BITS) | backoff;
return result;
Expand All @@ -63,12 +65,12 @@ static inline _Py_BackoffCounter
restart_backoff_counter(_Py_BackoffCounter counter)
{
assert(!is_unreachable_backoff_counter(counter));
int backoff = counter.value_and_backoff & 15;
int backoff = counter.value_and_backoff & BACKOFF_MASK;
if (backoff < MAX_BACKOFF) {
return make_backoff_counter((1 << (backoff + 1)) - 1, backoff + 1);
return make_backoff_counter((1 << (2 * backoff + 3)) - 1, backoff + 1);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are changing this, we might as well use a table. It is only 8 entries of 2 bytes each.

See #141498 (comment)
We want 0 to map to 1, and MAX_BACKOFF to map to near 8k.

I think you can make this branchless as well. Something like:

assert(backoff <= MAX_BACKOFF);
value = LOOKUP_TABLE[backoff]
backoff = backoff == MAX_BACKOFF ? backoff : backoff + 1;
return make_backoff_counter(value, backoff);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, if we store the counter, both value and backoff, in the table. It can be even simpler:

assert(backoff <= MAX_BACKOFF);
return LOOKUP_TABLE[backoff];

And have LOOKUP_TABLE[UNREACHABLE_BACKOFF] == LOOKUP_TABLE[MAX_BACKOFF] so non-debug builds do something sensible.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, I'll add using LOOKUP_TABLE in this PR.

}
else {
return make_backoff_counter((1 << MAX_BACKOFF) - 1, MAX_BACKOFF);
return make_backoff_counter((1 << (2 * MAX_BACKOFF + 1)) - 1, MAX_BACKOFF);
}
}

Expand Down Expand Up @@ -113,7 +115,7 @@ trigger_backoff_counter(void)
// as we always end up tracing the loop iteration's
// exhaustion iteration. Which aborts our current tracer.
#define JUMP_BACKWARD_INITIAL_VALUE 4000
#define JUMP_BACKWARD_INITIAL_BACKOFF 12
#define JUMP_BACKWARD_INITIAL_BACKOFF 6
static inline _Py_BackoffCounter
initial_jump_backoff_counter(void)
{
Expand All @@ -126,7 +128,7 @@ initial_jump_backoff_counter(void)
* otherwise when a side exit warms up we may construct
* a new trace before the Tier 1 code has properly re-specialized. */
#define SIDE_EXIT_INITIAL_VALUE 4000
#define SIDE_EXIT_INITIAL_BACKOFF 12
#define SIDE_EXIT_INITIAL_BACKOFF 6

static inline _Py_BackoffCounter
initial_temperature_backoff_counter(void)
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_opcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ def make_deferred_ref_count_obj():
class TestRacesDoNotCrash(TestBase):
# Careful with these. Bigger numbers have a higher chance of catching bugs,
# but you can also burn through a *ton* of type/dict/function versions:
ITEMS = 1000
ITEMS = 1400
Comment thread
efimov-mikhail marked this conversation as resolved.
LOOPS = 4
WRITERS = 2

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Change ``backoff counter`` to use powers of 4 instead of powers of 2.
This allows to support values up to 8191. Patch by Mikhail Efimov.
Loading