Skip to content

Commit ec736e7

Browse files
authored
GH-131798: Optimize cached class attributes and methods in the JIT (GH-134403)
1 parent 09e72cf commit ec736e7

File tree

12 files changed

+367
-132
lines changed

12 files changed

+367
-132
lines changed

Include/internal/pycore_backoff.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,10 @@ backoff_counter_triggers(_Py_BackoffCounter counter)
9595
return counter.value_and_backoff < UNREACHABLE_BACKOFF;
9696
}
9797

98-
/* Initial JUMP_BACKWARD counter.
99-
* This determines when we create a trace for a loop. */
98+
// Initial JUMP_BACKWARD counter.
99+
// Must be larger than ADAPTIVE_COOLDOWN_VALUE, otherwise when JIT code is
100+
// invalidated we may construct a new trace before the bytecode has properly
101+
// re-specialized:
100102
#define JUMP_BACKWARD_INITIAL_VALUE 4095
101103
#define JUMP_BACKWARD_INITIAL_BACKOFF 12
102104
static inline _Py_BackoffCounter

Include/internal/pycore_code.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,9 @@ write_location_entry_start(uint8_t *ptr, int code, int length)
451451
#define ADAPTIVE_COOLDOWN_BACKOFF 0
452452

453453
// Can't assert this in pycore_backoff.h because of header order dependencies
454+
#if JUMP_BACKWARD_INITIAL_VALUE <= ADAPTIVE_COOLDOWN_VALUE
455+
# error "JIT threshold value should be larger than adaptive cooldown value"
456+
#endif
454457
#if SIDE_EXIT_INITIAL_VALUE <= ADAPTIVE_COOLDOWN_VALUE
455458
# error "Cold exit value should be larger than adaptive cooldown value"
456459
#endif

Include/internal/pycore_uop_ids.h

Lines changed: 80 additions & 78 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_metadata.h

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_capi/test_opt.py

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,8 +1280,8 @@ class Bar:
12801280
self.assertIsNotNone(ex)
12811281
self.assertEqual(res, TIER2_THRESHOLD * 6 + 1)
12821282
call = opnames.index("_CALL_BUILTIN_FAST")
1283-
load_attr_top = opnames.index("_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", 0, call)
1284-
load_attr_bottom = opnames.index("_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", call)
1283+
load_attr_top = opnames.index("_POP_TOP_LOAD_CONST_INLINE_BORROW", 0, call)
1284+
load_attr_bottom = opnames.index("_POP_TOP_LOAD_CONST_INLINE_BORROW", call)
12851285
self.assertEqual(opnames[:load_attr_top].count("_GUARD_TYPE_VERSION"), 1)
12861286
self.assertEqual(opnames[call:load_attr_bottom].count("_CHECK_VALIDITY"), 2)
12871287

@@ -1303,8 +1303,8 @@ class Foo:
13031303
self.assertIsNotNone(ex)
13041304
self.assertEqual(res, TIER2_THRESHOLD * 2)
13051305
call = opnames.index("_CALL_BUILTIN_FAST_WITH_KEYWORDS")
1306-
load_attr_top = opnames.index("_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", 0, call)
1307-
load_attr_bottom = opnames.index("_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", call)
1306+
load_attr_top = opnames.index("_POP_TOP_LOAD_CONST_INLINE_BORROW", 0, call)
1307+
load_attr_bottom = opnames.index("_POP_TOP_LOAD_CONST_INLINE_BORROW", call)
13081308
self.assertEqual(opnames[:load_attr_top].count("_GUARD_TYPE_VERSION"), 1)
13091309
self.assertEqual(opnames[call:load_attr_bottom].count("_CHECK_VALIDITY"), 2)
13101310

@@ -2169,6 +2169,45 @@ def testfunc(n):
21692169
self.assertNotIn("_LOAD_SMALL_INT", uops)
21702170
self.assertIn("_LOAD_CONST_INLINE_BORROW", uops)
21712171

2172+
def test_cached_attributes(self):
2173+
class C:
2174+
A = 1
2175+
def m(self):
2176+
return 1
2177+
class D:
2178+
__slots__ = ()
2179+
A = 1
2180+
def m(self):
2181+
return 1
2182+
class E(Exception):
2183+
def m(self):
2184+
return 1
2185+
def f(n):
2186+
x = 0
2187+
c = C()
2188+
d = D()
2189+
e = E()
2190+
for _ in range(n):
2191+
x += C.A # _LOAD_ATTR_CLASS
2192+
x += c.A # _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES
2193+
x += d.A # _LOAD_ATTR_NONDESCRIPTOR_NO_DICT
2194+
x += c.m() # _LOAD_ATTR_METHOD_WITH_VALUES
2195+
x += d.m() # _LOAD_ATTR_METHOD_NO_DICT
2196+
x += e.m() # _LOAD_ATTR_METHOD_LAZY_DICT
2197+
return x
2198+
2199+
res, ex = self._run_with_optimizer(f, TIER2_THRESHOLD)
2200+
self.assertEqual(res, 6 * TIER2_THRESHOLD)
2201+
self.assertIsNotNone(ex)
2202+
uops = get_opnames(ex)
2203+
self.assertNotIn("_LOAD_ATTR_CLASS", uops)
2204+
self.assertNotIn("_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", uops)
2205+
self.assertNotIn("_LOAD_ATTR_NONDESCRIPTOR_NO_DICT", uops)
2206+
self.assertNotIn("_LOAD_ATTR_METHOD_WITH_VALUES", uops)
2207+
self.assertNotIn("_LOAD_ATTR_METHOD_NO_DICT", uops)
2208+
self.assertNotIn("_LOAD_ATTR_METHOD_LAZY_DICT", uops)
2209+
2210+
21722211
def global_identity(x):
21732212
return x
21742213

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve the JIT's ability to optimize away cached class attribute and method
2+
loads.

Python/bytecodes.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5307,6 +5307,18 @@ dummy_func(
53075307
value = PyStackRef_FromPyObjectBorrow(ptr);
53085308
}
53095309

5310+
tier2 op(_LOAD_CONST_UNDER_INLINE, (ptr/4, old -- value, new)) {
5311+
new = old;
5312+
DEAD(old);
5313+
value = PyStackRef_FromPyObjectNew(ptr);
5314+
}
5315+
5316+
tier2 op(_LOAD_CONST_UNDER_INLINE_BORROW, (ptr/4, old -- value, new)) {
5317+
new = old;
5318+
DEAD(old);
5319+
value = PyStackRef_FromPyObjectBorrow(ptr);
5320+
}
5321+
53105322
tier2 op(_CHECK_FUNCTION, (func_version/2 -- )) {
53115323
assert(PyStackRef_FunctionCheck(frame->f_funcobj));
53125324
PyFunctionObject *func = (PyFunctionObject *)PyStackRef_AsPyObjectBorrow(frame->f_funcobj);

Python/executor_cases.c.h

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)