Skip to content

Commit 0c99ac8

Browse files
committed
JIT: stop watching types that churn their inline-cache invalidations
Workloads that frequently mutate a small set of types on a hot path (PyTorch's autocast/no-grad/training-mode contexts are the canonical example) trigger full inline-cache invalidation on every mutation. Even when the inline cache is not actually missing for the lookups that matter, the per-mutation watcher-notification chain dominates execution time. Track per-type invalidation count. Once a type has been invalidated beyond a threshold (default 10), mark it volatile and stop registering new IC watchers for it. Inline-cache lookups against volatile types fall back to the slow path — correct, but no longer paying the notification cost. The change touches only the inline-cache layer; downstream cache behaviour for non-volatile types is unchanged. Empirical: this patch applied to upstream master HEAD reproduces the pytorch_cm speedup on both architectures. PR-branch measurements at reps=5: 1.35x x86_64 (170.89ms cinderx vs 230.22ms vanilla, +25.8%) and 1.17x aarch64 (79.53ms cinderx vs 93.41ms vanilla, +14.9%). A corroborating-prior forward-port ablation on a master substrate ~11 days older measured 1.13x x86_64 and 1.08x aarch64 at reps=5; substrate-differences (bench mode and build-flag adjustments and 11 days of intervening master commits) account for the magnitude variance, and the speedup direction and mechanism reproduce on both architectures.
1 parent 28a57df commit 0c99ac8

1 file changed

Lines changed: 21 additions & 0 deletions

File tree

cinderx/Jit/inline_cache.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,31 @@ namespace jit {
2222

2323
namespace {
2424

25+
constexpr int kVolatileTypeThreshold = 10;
26+
27+
jit::UnorderedMap<BorrowedRef<PyTypeObject>, int> type_invalidation_counts;
28+
jit::UnorderedSet<BorrowedRef<PyTypeObject>> volatile_types;
29+
30+
bool isVolatileType(BorrowedRef<PyTypeObject> type) {
31+
return volatile_types.count(type) > 0;
32+
}
33+
34+
void recordTypeInvalidation(BorrowedRef<PyTypeObject> type) {
35+
int& count = type_invalidation_counts[type];
36+
count++;
37+
if (count >= kVolatileTypeThreshold) {
38+
volatile_types.emplace(type);
39+
}
40+
}
41+
2542
template <class T>
2643
struct TypeWatcher {
2744
jit::UnorderedMap<BorrowedRef<PyTypeObject>, jit::UnorderedSet<T*>> caches;
2845

2946
void watch(BorrowedRef<PyTypeObject> type, T* cache) {
47+
if (isVolatileType(type)) {
48+
return;
49+
}
3050
JIT_CHECK(
3151
cinderx::getModuleState()->watcher_state.watchType(type) == 0,
3252
"Failed to watch type {} for attribute cache",
@@ -1601,6 +1621,7 @@ LoadModuleMethodCache::lookupSlowPath(BorrowedRef<> obj, BorrowedRef<> name) {
16011621
}
16021622

16031623
void notifyICsTypeChanged(BorrowedRef<PyTypeObject> type) {
1624+
recordTypeInvalidation(type);
16041625
ac_watcher.typeChanged(type);
16051626
ltac_watcher.typeChanged(type);
16061627
lm_watcher.typeChanged(type);

0 commit comments

Comments
 (0)