Skip to content

Commit a3b78a3

Browse files
gh-141498: Change backoff counter to use prime numbers instead of powers of 2 (GH-141591)
1 parent fb26d9c commit a3b78a3

File tree

3 files changed

+44
-29
lines changed

3 files changed

+44
-29
lines changed

Include/internal/pycore_backoff.h

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,48 @@ extern "C" {
2222
Another use is for the Tier 2 optimizer to decide when to create
2323
a new Tier 2 trace (executor). Again, exponential backoff is used.
2424
25-
The 16-bit counter is structured as a 12-bit unsigned 'value'
26-
and a 4-bit 'backoff' field. When resetting the counter, the
25+
The 16-bit counter is structured as a 13-bit unsigned 'value'
26+
and a 3-bit 'backoff' field. When resetting the counter, the
2727
backoff field is incremented (until it reaches a limit) and the
28-
value is set to a bit mask representing the value 2**backoff - 1.
29-
The maximum backoff is 12 (the number of bits in the value).
28+
value is set to a bit mask representing some prime value - 1.
29+
New values and backoffs for each backoff are calculated once
30+
at compile time and saved to value_and_backoff_next table.
31+
The maximum backoff is 6, since 7 is an UNREACHABLE_BACKOFF.
3032
3133
There is an exceptional value which must not be updated, 0xFFFF.
3234
*/
3335

34-
#define BACKOFF_BITS 4
35-
#define MAX_BACKOFF 12
36-
#define UNREACHABLE_BACKOFF 15
37-
38-
static inline bool
39-
is_unreachable_backoff_counter(_Py_BackoffCounter counter)
40-
{
41-
return counter.value_and_backoff == UNREACHABLE_BACKOFF;
42-
}
36+
#define BACKOFF_BITS 3
37+
#define BACKOFF_MASK 7
38+
#define MAX_BACKOFF 6
39+
#define UNREACHABLE_BACKOFF 7
40+
#define MAX_VALUE 0x1FFF
41+
42+
#define MAKE_VALUE_AND_BACKOFF(value, backoff) \
43+
((value << BACKOFF_BITS) | backoff)
44+
45+
// For previous backoff b we use value x such that
46+
// x + 1 is near to 2**(2*b+1) and x + 1 is prime.
47+
static const uint16_t value_and_backoff_next[] = {
48+
MAKE_VALUE_AND_BACKOFF(1, 1),
49+
MAKE_VALUE_AND_BACKOFF(6, 2),
50+
MAKE_VALUE_AND_BACKOFF(30, 3),
51+
MAKE_VALUE_AND_BACKOFF(126, 4),
52+
MAKE_VALUE_AND_BACKOFF(508, 5),
53+
MAKE_VALUE_AND_BACKOFF(2052, 6),
54+
// We use the same backoff counter for all backoffs >= MAX_BACKOFF.
55+
MAKE_VALUE_AND_BACKOFF(8190, 6),
56+
MAKE_VALUE_AND_BACKOFF(8190, 6),
57+
};
4358

4459
static inline _Py_BackoffCounter
4560
make_backoff_counter(uint16_t value, uint16_t backoff)
4661
{
47-
assert(backoff <= 15);
48-
assert(value <= 0xFFF);
49-
_Py_BackoffCounter result;
50-
result.value_and_backoff = (value << BACKOFF_BITS) | backoff;
51-
return result;
62+
assert(backoff <= UNREACHABLE_BACKOFF);
63+
assert(value <= MAX_VALUE);
64+
return ((_Py_BackoffCounter){
65+
.value_and_backoff = MAKE_VALUE_AND_BACKOFF(value, backoff)
66+
});
5267
}
5368

5469
static inline _Py_BackoffCounter
@@ -62,14 +77,11 @@ forge_backoff_counter(uint16_t counter)
6277
static inline _Py_BackoffCounter
6378
restart_backoff_counter(_Py_BackoffCounter counter)
6479
{
65-
assert(!is_unreachable_backoff_counter(counter));
66-
int backoff = counter.value_and_backoff & 15;
67-
if (backoff < MAX_BACKOFF) {
68-
return make_backoff_counter((1 << (backoff + 1)) - 1, backoff + 1);
69-
}
70-
else {
71-
return make_backoff_counter((1 << MAX_BACKOFF) - 1, MAX_BACKOFF);
72-
}
80+
uint16_t backoff = counter.value_and_backoff & BACKOFF_MASK;
81+
assert(backoff <= MAX_BACKOFF);
82+
return ((_Py_BackoffCounter){
83+
.value_and_backoff = value_and_backoff_next[backoff]
84+
});
7385
}
7486

7587
static inline _Py_BackoffCounter
@@ -113,7 +125,7 @@ trigger_backoff_counter(void)
113125
// as we always end up tracing the loop iteration's
114126
// exhaustion iteration. Which aborts our current tracer.
115127
#define JUMP_BACKWARD_INITIAL_VALUE 4000
116-
#define JUMP_BACKWARD_INITIAL_BACKOFF 12
128+
#define JUMP_BACKWARD_INITIAL_BACKOFF 6
117129
static inline _Py_BackoffCounter
118130
initial_jump_backoff_counter(void)
119131
{
@@ -126,7 +138,7 @@ initial_jump_backoff_counter(void)
126138
* otherwise when a side exit warms up we may construct
127139
* a new trace before the Tier 1 code has properly re-specialized. */
128140
#define SIDE_EXIT_INITIAL_VALUE 4000
129-
#define SIDE_EXIT_INITIAL_BACKOFF 12
141+
#define SIDE_EXIT_INITIAL_BACKOFF 6
130142

131143
static inline _Py_BackoffCounter
132144
initial_temperature_backoff_counter(void)

Lib/test/test_opcache.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ def make_deferred_ref_count_obj():
590590
class TestRacesDoNotCrash(TestBase):
591591
# Careful with these. Bigger numbers have a higher chance of catching bugs,
592592
# but you can also burn through a *ton* of type/dict/function versions:
593-
ITEMS = 1000
593+
ITEMS = 1400
594594
LOOPS = 4
595595
WRITERS = 2
596596

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Change ``backoff counter`` to use prime numbers instead of powers of 2.
2+
Use only 3 bits for ``counter`` and 13 bits for ``value``.
3+
This allows to support values up to 8191. Patch by Mikhail Efimov.

0 commit comments

Comments
 (0)