Optimize compiler execution hot paths - #10100
Conversation
…e validation - Replace slow runtime_checkable Protocol isinstance check in unwrap_reference() with getattr(), avoiding ~60x overhead per call for non-matching types (strings, ints, bools). This speeds up type validation by 3.5x. - Cache entity attribute lookups and default values after normalization, eliminating repeated parent-chain walks during instance creation (25% faster Instance.__init__). - Fast-path eager promise handling for statements with no promises (the common case). Measured 10% speedup on connect-infra compile (7.54s → 6.78s) and 6% on synthetic benchmarks (5.91s → 5.56s for 500-device model). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Entity.get_all_attributes() public method instead of accessing _all_attributes_cache directly from runtime.py - Add proper Callable type annotation for getattr result in references.py - Rename variable to avoid mypy type narrowing issue across branches - Add changelog entry for the performance improvement Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The scaling tests https://code.inmanta.com/integration/compilerscaling/-/merge_requests/9 |
…string formatter - Add fast-path in String.validate and Bool.validate using exact type checks to skip super().validate() for the common case - Add fast-path in TypeReferenceUnion.validate to skip unwrap_reference() for primitive Python types - Restructure add_to_index: inline index_value_gate, add early break on unready attributes, avoid rebuilding filtered dict per call - Cache frozenset of index attributes to avoid set() creation in lookup_index (245K calls/compile) - Reuse stateless FStringFormatter as module-level singleton instead of allocating per f-string resolution (995K allocs/compile) Profiled with compilerscaling benchmark at 5000 elines: master: 122.2s, 234M calls compiler-perf: 96.4s, 190M calls (21% faster) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sanderr
left a comment
There was a problem hiding this comment.
Only reviewed eager promise changes for now.
| # Fast path: most statements have no eager promises | ||
| if not self._own_eager_promises: | ||
| return {} |
There was a problem hiding this comment.
This is the type of construct I really don't like because of how it's coupled: it builds on a non-trivial and undocumented assumption. I do agree that on the critical path, as we are here, performance may be the more important driver. But, in this case I see no reason we can't get performance and readability both. Why don't we just inline it here?
This suggestion is based on the assumption that it's not the list comprehension that is computationally expensive, but the indirection via two method calls. Please verify that assumption.
Additionally, could we try to make _own_eager_promises non-private (own_eager_promises) so that it can be used directly rather than through get_own_eager_promises(). That method was a nice contrast to get_all_eager_promises(), but it's really not a strict requirement. If you make this change, please make sure to document the attribute.
There was a problem hiding this comment.
Claude here on behalf of Bart.
Verified the assumption: the method call indirection is indeed the expensive part (30% overhead in a microbenchmark of 5M calls), not the list comprehension.
Addressed in 1044994:
- Inlined
schedule_eager_promisesinto_requires_emit_promises - Renamed
_own_eager_promisestoown_eager_promises(public) with a docstring - Removed
get_own_eager_promises()(was never called externally)
There was a problem hiding this comment.
Does the if not self.own_eager_promises still gain us anything (non-negligible)? If not, I'd drop it. If it does, we can drop the conditional expression in the return statement because the else branch will be unreachable.
There was a problem hiding this comment.
Claude here on behalf of Bart.
Yes, the guard is still worthwhile: it avoids the list comprehension + dict construction + WrappedValueVariable allocation for the majority of statements that have no eager promises. Dropped the unreachable conditional on the return statement as you suggested — since the early return handles the empty case, promises is always truthy after the comprehension.
Fixed in bdfd904.
| Uses an ``in`` check instead of try/except KeyError because most statements have no eager | ||
| promises, making the common-case miss cheaper (avoids exception allocation overhead). |
There was a problem hiding this comment.
This is an implementation comment that does not belong in a docstring. Should be a plain comment above the relevant line.
There was a problem hiding this comment.
Claude here on behalf of Bart.
Fixed in 1044994 — moved the rationale to a plain comment above the requires.get() call.
| try: | ||
| promises = requires[(self, EagerPromise)] | ||
| except KeyError: | ||
| key = (self, EagerPromise) |
There was a problem hiding this comment.
Is this variable here to satisfy mypy on the requires[key] access? How does requires.get((self, EagerPromise)) followed by if promises is None compare, performance-wise? It doesn't require the key variable, and reads ever so slightly easier imo, but I have no clue how dict.get() is implemented (I assume efficiently).
There was a problem hiding this comment.
Claude here on behalf of Bart.
Good suggestion — dict.get() is implemented in C and is efficient. Switched to requires.get((self, EagerPromise)) with if promises is None in 1044994. Reads cleaner and avoids the intermediate variable.
There was a problem hiding this comment.
Claude here on behalf of Bart.
Switched to requires.get() as suggested. However, since requires is typed as dict[object, object], .get() returns object which mypy can't assign to Sequence[EagerPromise]. Used # type: ignore[assignment] for now — do you have a preferred way to handle this? Options would be:
- Keep the
type: ignore(current) - Use a
cast() - Tighten the type of the
requiresdict (would be a larger change)
There was a problem hiding this comment.
This is indeed one of the instances where our type coverage is still lacking. Rather than type ignores, our usual approach for these types of issues is to accept them in the mypy-baseline file. Our makefile contains a target to regenerate it.
There was a problem hiding this comment.
Claude here on behalf of Bart.
Good to know — removed the # type: ignore[assignment] and confirmed the existing mypy baseline entry covers it (0 new errors when running through mypy_baseline filter).
Fixed in bdfd904.
…es public - Inline schedule_eager_promises into _requires_emit_promises to remove one level of method call indirection on the hot path - Rename _own_eager_promises to own_eager_promises (public attribute) and remove the get_own_eager_promises() accessor - Move implementation rationale from _fulfill_promises docstring to a plain comment - Use requires.get() instead of separate key variable + in check in _fulfill_promises Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dict.get() returns `object` for dict[object, object], causing a mypy assignment error. Reverted to `in` check + indexing which mypy can narrow correctly. Also fixed Black formatting issue from the inlined list comprehension. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| Should only be called after normalization. | ||
| """ | ||
| return self._own_eager_promises | ||
| #: Eager promises this statement itself is responsible for. Set during normalization. |
There was a problem hiding this comment.
| #: Eager promises this statement itself is responsible for. Set during normalization. | |
| # All Eager promises this statement itself is responsible for. Set during normalization. |
There was a problem hiding this comment.
Claude here on behalf of Bart.
Applied the suggestion. Fixed in bdfd904.
- Fix comment wording on own_eager_promises (s/Eager/eager/ kept as reviewer suggested capitalized "Eager") - Remove unreachable conditional in _requires_emit_promises return (early return handles empty case, so promises is always truthy) - Replace type: ignore with mypy baseline for requires.get() assignment Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Claude through @bartv, I have another potential improvement in mind. Could you estimate and / or verify the impact on your representative model? My suggestion builds on the assumption that real world models have to regularly reschedule plugins through |
Problem When a plugin accesses an unset model attribute, it raises UnsetException and gets rescheduled. On reschedule, check_args() runs again with the exact same arguments — redoing all type validation (expected_type.validate(value)) and domain conversion (DynamicProxy.return_value()). In the pathological case from the logs, systemtenant::empty_source_or_destination was rescheduled 6,892 times from a single call site, each time Why caching is safe
So it seems feasible, I will see if I can something like this to the compiler scaling tests |
Benchmark: plugin
|
| Metric | No cache | Cached | Savings |
|---|---|---|---|
| Overall | |||
| Total time | 16.884s | 16.152s | 0.732s (4.3%) |
| Total function calls | 44,718,173 | 41,873,248 | 2,844,925 (6.4%) |
| Plugin validation path | |||
check_args calls |
80,002 | 45,002 | 35,000 (44%) |
check_args time |
0.437s | 0.337s | 0.100s (23%) |
validate_and_convert calls |
200,003 | 110,003 | 90,000 (45%) |
validate_and_convert time |
0.257s | 0.132s | 0.125s (49%) |
convert_and_validate calls |
200,003 | 110,003 | 90,000 (45%) |
convert_and_validate time |
0.087s | 0.045s | 0.042s (48%) |
return_value calls |
460,003 | 370,003 | 90,000 (20%) |
return_value time |
0.574s | 0.376s | 0.198s (34%) |
| Validation path total | 1.355s | 0.890s | 0.465s (34%) |
No regression on compilerscaling (1000 elines, 0 reschedules): 5.70s → 5.64s (within noise).
🤖 Generated with Claude Code on behalf of Bart
When a plugin is rescheduled after UnsetException, the same arguments are re-validated by check_args unnecessarily. Cache the validated ProcessedArgs in a mutable single-element list on FunctionUnit, reusing it on subsequent calls. The Context insertion is also skipped on cache hit since it was already inserted into the cached args list. Profiling shows check_args is only ~0.5% of compile time, so the practical impact is negligible, but the optimization is correct and avoids redundant work on the principle of not repeating deterministic computations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add fast paths for primitive types (str, int, float, bool) across several frequently-called methods in the compiler: - Type.validate(): Add exact type checks for Integer, Float, and Number that skip the super().validate() Reference check (same pattern as Bool/String) - DynamicProxy.return_value(): Return immutable primitives directly instead of calling copy() (copy for immutable types is a no-op with function overhead) - DynamicProxy.unwrap(): Fast-path for primitives to skip 6 isinstance checks - validate_and_convert_to_python_domain(): Fast-path for primitives to skip NoneValue check, has_custom_to_python(), ProxyContext allocation - Plugin.call_in_context(): Skip DynamicUnwrapContext/functools.partial creation when return value is a primitive - Scheduler.run(): Guard LOGGER.log(LOG_LEVEL_TRACE) calls with isEnabledFor() to avoid argument evaluation when trace logging is disabled Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| count, | ||
| now - prev, | ||
| ) | ||
| if LOGGER.isEnabledFor(LOG_LEVEL_TRACE): |
There was a problem hiding this comment.
Micro benchmarks show that this is still 2x faster. This also shortcuts the len() calls on the 3 sets
The cache was typed as list[object], causing mypy attr-defined errors when accessing .args/.kwargs on the cached value. Use proper CheckedArgs type and extract to a local variable for mypy narrowing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The zerowaiters partitioning code called get_progress_potential() twice per element (once per generator expression). Replaced with a single loop that partitions into has_potential/new_zerowaiters lists, halving the number of calls (5.7M -> 2.9M on inmanta_infra benchmark). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The has_relation_precedence_rules() method was called 3.3M+ times per compile via get_progress_potential(), each time evaluating bool(self.freeze_dependents) on an empty set. Cache the result as a plain bool flag set once in add_freeze_dependent(). Access it directly in get_progress_potential() to eliminate the method call overhead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the recursive closure resolve_proxies() inside find_wait_cycle with an iterative static method _resolve_proxy(). Eliminates 1.8M recursive call frames on the systemtenant benchmark (4.7M -> 2.9M primitive calls). Also avoids closure re-creation on each of the 239 find_wait_cycle invocations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cProfile analysis: per-optimization function call reductionsProfiled each optimization branch in isolation against master using cProfile on all 5 real-world benchmarks. Function call count delta vs master
Absolute function call counts (millions)
Top eliminated functions (gpp-dedup on systemtenant: 113.8M → 88.0M, -25.8M calls)
The cascade: deduplicating the Key observations
|
Profile comparison: systemtenant full model (1909 services) with dependency managerProfiled on the benchmark VM with cProfile. Both runs use the systemtenant Total: 1100s (master) → 693s (compiler-perf) — 37% faster Top functions by self time
Key observations
Wall-clock benchmarks (full production model, 1909 services)
🤖 Generated with Claude Code |
…o avoid argument evaluation when disabled (PR #10161) ## Summary Guard `LOGGER.log(LOG_LEVEL_TRACE)` calls in the scheduler main loop with `isEnabledFor()` to avoid argument evaluation (including `len()` calls on queues) when trace logging is disabled. Split from #10100. ## Benchmark results (10 runs avg, dedicated benchmark machine) | Benchmark | master | with opt | Delta | | -------------- | -----: | -------: | -----: | | athonet_mpn | 14.89s | 15.41s | +3.5% | | connect_demo | 7.98s | 8.11s | +1.6% | | connect_infra | 16.96s | 16.70s | -1.5% | | inmanta_infra | 15.29s | 15.04s | -1.6% | | systemtenant | 11.75s | 11.77s | +0.2% | | **Total** | 66.87s | 67.03s | +0.2% | Impact is within noise when isolated. The `isEnabledFor` guard avoids evaluating `len()` on queues per scheduler iteration. ## Test plan - [x] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…o avoid argument evaluation when disabled (PR #10161) ## Summary Guard `LOGGER.log(LOG_LEVEL_TRACE)` calls in the scheduler main loop with `isEnabledFor()` to avoid argument evaluation (including `len()` calls on queues) when trace logging is disabled. Split from #10100. ## Benchmark results (10 runs avg, dedicated benchmark machine) | Benchmark | master | with opt | Delta | | -------------- | -----: | -------: | -----: | | athonet_mpn | 14.89s | 15.41s | +3.5% | | connect_demo | 7.98s | 8.11s | +1.6% | | connect_infra | 16.96s | 16.70s | -1.5% | | inmanta_infra | 15.29s | 15.04s | -1.6% | | systemtenant | 11.75s | 11.77s | +0.2% | | **Total** | 66.87s | 67.03s | +0.2% | Impact is within noise when isolated. The `isEnabledFor` guard avoids evaluating `len()` on queues per scheduler iteration. ## Test plan - [x] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code)
… partitioning loop (PR #10162) ## Summary The zerowaiters partitioning code called `get_progress_potential()` twice per element (once per generator expression). Replaced with a single loop that computes the value once and partitions into two lists. Halves the call count (5.7M → 2.9M on inmanta_infra). Split from #10100. ## Benchmark results (10 runs avg, dedicated benchmark machine) | Benchmark | master | with opt | Delta | | -------------- | -----: | -------: | ------: | | athonet_mpn | 15.45s | 15.20s | -1.6% | | connect_demo | 8.20s | 8.18s | -0.2% | | connect_infra | 16.68s | 16.75s | +0.4% | | inmanta_infra | 15.24s | 14.11s | -7.4% | | systemtenant | 11.92s | 10.02s | -15.9% | | **Total** | 67.49s | 64.26s | -4.8% | Significant impact on models with heavy speculation (systemtenant -15.9%, inmanta_infra -7.4%). These projects have many `[0:]` relations that trigger the zerowaiters partitioning path frequently. ## Test plan - [x] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code)
… partitioning loop (PR #10162) ## Summary The zerowaiters partitioning code called `get_progress_potential()` twice per element (once per generator expression). Replaced with a single loop that computes the value once and partitions into two lists. Halves the call count (5.7M → 2.9M on inmanta_infra). Split from #10100. ## Benchmark results (10 runs avg, dedicated benchmark machine) | Benchmark | master | with opt | Delta | | -------------- | -----: | -------: | ------: | | athonet_mpn | 15.45s | 15.20s | -1.6% | | connect_demo | 8.20s | 8.18s | -0.2% | | connect_infra | 16.68s | 16.75s | +0.4% | | inmanta_infra | 15.24s | 14.11s | -7.4% | | systemtenant | 11.92s | 10.02s | -15.9% | | **Total** | 67.49s | 64.26s | -4.8% | Significant impact on models with heavy speculation (systemtenant -15.9%, inmanta_infra -7.4%). These projects have many `[0:]` relations that trigger the zerowaiters partitioning path frequently. ## Test plan - [x] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code)
… allocating per f-string resolution (PR #10158) ## Summary Reuse stateless `FStringFormatter` as module-level singleton instead of allocating per f-string resolution (995K allocs/compile). Split from #10100. ## Benchmark results (10 runs avg, dedicated benchmark machine) | Benchmark | master | with opt | Delta | | -------------- | -----: | -------: | -----: | | athonet_mpn | 14.39s | 14.41s | +0.1% | | connect_demo | 7.72s | 7.71s | -0.1% | | connect_infra | 15.68s | 15.66s | -0.1% | | inmanta_infra | 14.06s | 14.05s | -0.1% | | systemtenant | 10.95s | 10.99s | +0.4% | | **Total** | 62.80s | 62.82s | +0.0% | Impact is within noise when isolated. This micro-optimization saves one object allocation per f-string resolution; the real cost is in `vformat()` itself. Impact is only visible in cProfile (0.35s at 5000 elines in the synthetic compilerscaling benchmark). ## Test plan - [x] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code)
… allocating per f-string resolution (PR #10158) ## Summary Reuse stateless `FStringFormatter` as module-level singleton instead of allocating per f-string resolution (995K allocs/compile). Split from #10100. ## Benchmark results (10 runs avg, dedicated benchmark machine) | Benchmark | master | with opt | Delta | | -------------- | -----: | -------: | -----: | | athonet_mpn | 14.39s | 14.41s | +0.1% | | connect_demo | 7.72s | 7.71s | -0.1% | | connect_infra | 15.68s | 15.66s | -0.1% | | inmanta_infra | 14.06s | 14.05s | -0.1% | | systemtenant | 10.95s | 10.99s | +0.4% | | **Total** | 62.80s | 62.82s | +0.0% | Impact is within noise when isolated. This micro-optimization saves one object allocation per f-string resolution; the real cost is in `vformat()` itself. Impact is only visible in cProfile (0.35s at 5000 elines in the synthetic compilerscaling benchmark). ## Test plan - [x] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…in find_wait_cycle (PR #10164) ## Summary The `find_wait_cycle` method used a recursive closure `resolve_proxies()` to unwrap `ResultVariableProxy` chains. On a customer project benchmark, this was called 4.7M times (2.9M primitive + 1.8M recursive). Replaced with an iterative static method `_resolve_proxy()`, eliminating recursive call overhead and closure re-creation on each `find_wait_cycle` invocation. Split from #10100. ## Benchmark results (10 runs avg, dedicated benchmark machine) | Benchmark | master | with opt | Delta | | -------------- | -----: | -------: | -----: | | athonet_mpn | 15.17s | 15.29s | +0.8% | | connect_demo | 7.96s | 8.07s | +1.4% | | connect_infra | 16.51s | 16.58s | +0.4% | | inmanta_infra | 14.98s | 14.82s | -1.1% | | systemtenant | 11.48s | 11.91s | +3.7% | | **Total** | 66.10s | 66.67s | +0.9% | Impact is within noise when isolated. This optimization reduces overhead in the `find_wait_cycle` path which is primarily exercised during scheduler speculation. Its effect becomes pronounced when combined with the batch freezing optimization (PR #10152). ## Test plan - [x] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…in find_wait_cycle (PR #10164) ## Summary The `find_wait_cycle` method used a recursive closure `resolve_proxies()` to unwrap `ResultVariableProxy` chains. On a customer project benchmark, this was called 4.7M times (2.9M primitive + 1.8M recursive). Replaced with an iterative static method `_resolve_proxy()`, eliminating recursive call overhead and closure re-creation on each `find_wait_cycle` invocation. Split from #10100. ## Benchmark results (10 runs avg, dedicated benchmark machine) | Benchmark | master | with opt | Delta | | -------------- | -----: | -------: | -----: | | athonet_mpn | 15.17s | 15.29s | +0.8% | | connect_demo | 7.96s | 8.07s | +1.4% | | connect_infra | 16.51s | 16.58s | +0.4% | | inmanta_infra | 14.98s | 14.82s | -1.1% | | systemtenant | 11.48s | 11.91s | +3.7% | | **Total** | 66.10s | 66.67s | +0.9% | Impact is within noise when isolated. This optimization reduces overhead in the `find_wait_cycle` path which is primarily exercised during scheduler speculation. Its effect becomes pronounced when combined with the batch freezing optimization (PR #10152). ## Test plan - [x] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…call overhead in get_progress_potential (PR #10163) ## Summary `has_relation_precedence_rules()` was called 3.3M+ times per compile via `get_progress_potential()`, each time evaluating `bool(self.freeze_dependents)` on an empty set. Cache the result as a public bool attribute `has_freeze_dependents` set once in `add_freeze_dependent()`. Access it directly in `get_progress_potential()` with an explicit `int()` cast for clarity. Split from #10100. ## Benchmark results (10 runs avg, dedicated benchmark machine) | Benchmark | master | with opt | Delta | | -------------- | -----: | -------: | -----: | | athonet_mpn | 14.82s | 15.26s | +3.0% | | connect_demo | 7.95s | 8.14s | +2.4% | | connect_infra | 16.56s | 16.66s | +0.6% | | inmanta_infra | 14.97s | 14.74s | -1.5% | | systemtenant | 12.05s | 11.28s | -6.4% | | **Total** | 66.35s | 66.08s | -0.4% | Impact is most visible on systemtenant (-6.4%) which has heavy scheduler speculation. The method call elimination saves ~0.43s per compile on models with 3.3M+ `get_progress_potential` calls. ## Test plan - [x] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…call overhead in get_progress_potential (PR #10163) ## Summary `has_relation_precedence_rules()` was called 3.3M+ times per compile via `get_progress_potential()`, each time evaluating `bool(self.freeze_dependents)` on an empty set. Cache the result as a public bool attribute `has_freeze_dependents` set once in `add_freeze_dependent()`. Access it directly in `get_progress_potential()` with an explicit `int()` cast for clarity. Split from #10100. ## Benchmark results (10 runs avg, dedicated benchmark machine) | Benchmark | master | with opt | Delta | | -------------- | -----: | -------: | -----: | | athonet_mpn | 14.82s | 15.26s | +3.0% | | connect_demo | 7.95s | 8.14s | +2.4% | | connect_infra | 16.56s | 16.66s | +0.6% | | inmanta_infra | 14.97s | 14.74s | -1.5% | | systemtenant | 12.05s | 11.28s | -6.4% | | **Total** | 66.35s | 66.08s | -0.4% | Impact is most visible on systemtenant (-6.4%) which has heavy scheduler speculation. The method call elimination saves ~0.43s per compile on models with 3.3M+ `get_progress_potential` calls. ## Test plan - [x] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…tion for str/int/float/bool (PR #10156) ## Summary Add `type(value) is str` / `bool` / `int` / `float` fast-paths in `String.validate`, `Bool.validate`, `Integer.validate`, `Float.validate`, `Number.validate`, and `TypeReferenceUnion.validate` to skip full validation for primitive types (1M+ calls/compile). Split from #10100. ## Benchmark results (10 runs avg, dedicated benchmark machine) | Benchmark | master | with opt | Delta | | -------------- | -----: | -------: | -----: | | athonet_mpn | 14.38s | 14.23s | -1.0% | | connect_demo | 7.74s | 7.72s | -0.3% | | connect_infra | 15.66s | 15.47s | -1.2% | | inmanta_infra | 14.06s | 13.58s | -3.4% | | systemtenant | 10.92s | 10.82s | -0.9% | | **Total** | 62.76s | 61.82s | -1.5% | ## Test plan - [x] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…tion for str/int/float/bool (PR #10156) ## Summary Add `type(value) is str` / `bool` / `int` / `float` fast-paths in `String.validate`, `Bool.validate`, `Integer.validate`, `Float.validate`, `Number.validate`, and `TypeReferenceUnion.validate` to skip full validation for primitive types (1M+ calls/compile). Split from #10100. ## Benchmark results (10 runs avg, dedicated benchmark machine) | Benchmark | master | with opt | Delta | | -------------- | -----: | -------: | -----: | | athonet_mpn | 14.38s | 14.23s | -1.0% | | connect_demo | 7.74s | 7.72s | -0.3% | | connect_infra | 15.66s | 15.47s | -1.2% | | inmanta_infra | 14.06s | 13.58s | -3.4% | | systemtenant | 10.92s | 10.82s | -0.9% | | **Total** | 62.76s | 61.82s | -1.5% | ## Test plan - [x] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…h getattr for 60x speedup on non-matching types (PR #10155) ## Summary Replace slow `runtime_checkable` Protocol `isinstance` check in `unwrap_reference()` with `getattr()`, avoiding ~60× overhead per call for non-matching types (strings, ints, bools). This eliminates `typing.__instancecheck__`, `inspect._shadowed_dict`, and `inspect.getattr_static` from the profile entirely. Split from #10100. ## Benchmark results (10 runs avg, dedicated benchmark machine) | Benchmark | master | with opt | Delta | | -------------- | -----: | -------: | -----: | | athonet_mpn | 14.37s | 14.02s | -2.4% | | connect_demo | 7.74s | 7.64s | -1.3% | | connect_infra | 15.66s | 15.35s | -2.0% | | inmanta_infra | 14.03s | 13.56s | -3.3% | | systemtenant | 10.98s | 11.05s | +0.6% | | **Total** | 62.78s | 61.62s | -1.8% | ## Test plan - [x] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…h getattr for 60x speedup on non-matching types (PR #10155) ## Summary Replace slow `runtime_checkable` Protocol `isinstance` check in `unwrap_reference()` with `getattr()`, avoiding ~60× overhead per call for non-matching types (strings, ints, bools). This eliminates `typing.__instancecheck__`, `inspect._shadowed_dict`, and `inspect.getattr_static` from the profile entirely. Split from #10100. ## Benchmark results (10 runs avg, dedicated benchmark machine) | Benchmark | master | with opt | Delta | | -------------- | -----: | -------: | -----: | | athonet_mpn | 14.37s | 14.02s | -2.4% | | connect_demo | 7.74s | 7.64s | -1.3% | | connect_infra | 15.66s | 15.35s | -2.0% | | inmanta_infra | 14.03s | 13.56s | -3.3% | | systemtenant | 10.98s | 11.05s | +0.6% | | **Total** | 62.78s | 61.62s | -1.8% | ## Test plan - [x] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…chain walks during instance creation (PR #10154) ## Summary Cache `get_all_attribute_names` and `get_default_values` results to avoid repeated parent-chain MRO walks during instance creation. Add fast-path for `_requires_emit_promises` / `_fulfill_promises` when no eager promises exist (the common case). Split from #10100. ## Benchmark results (10 runs avg, dedicated benchmark machine) | Benchmark | master | with opt | Delta | | -------------- | -----: | -------: | -----: | | athonet_mpn | 14.40s | 15.87s | +10.2% | | connect_demo | 7.78s | 7.77s | -0.1% | | connect_infra | 15.75s | 15.48s | -1.7% | | inmanta_infra | 14.11s | 13.87s | -1.7% | | systemtenant | 11.04s | 10.86s | -1.6% | | **Total** | 63.08s | 63.85s | +1.2% | Impact is within noise when isolated. This optimization primarily benefits models with deep entity inheritance hierarchies and many instances. Its effect becomes more pronounced when combined with other optimizations (visible in the combined compiler-perf branch benchmarks). ## Test plan - [x] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code)

Status
This PR has been split into individual PRs per optimization category. See below for the full list and benchmark results.
Per-optimization isolated benchmark results (10 runs avg, dedicated benchmark machine)
Note: Most optimizations show their effect only when combined. The athonet_mpn outliers (+10%) are a consistent warm-up artifact (first benchmark after venv rebuild). The gpp-dedup (#10162) is the clear standalone winner.
Individual PRs
Entity/statement optimizations:
Plugin optimizations:
Scheduler optimizations:
Related PRs (separate branches):
🤖 Generated with Claude Code