perf: store the fulfillment engine inline in ObligationCtxt - #160268
perf: store the fulfillment engine inline in ObligationCtxt#160268xmakro wants to merge 1 commit into
Conversation
Every ObligationCtxt heap-allocated its fulfillment engine as a Box<dyn TraitEngine>, making it the single largest allocation site in the compiler (161k allocations on a syn check build, created per candidate probe in method resolution among others). The solver choice is a per-session constant and both engine types are small, so store them inline in a two-variant enum with static dispatch. The enum's TraitEngine impl needs both FromSolverError bounds, which ripples to the generic impl blocks and two generic users; the concrete error types used everywhere implement both. The boxed engine remains for the per-body typeck root fulfillment context.
|
related: #155714 |
|
@bors try @rust-timer queue |
This comment has been minimized.
This comment has been minimized.
|
@bors try |
This comment has been minimized.
This comment has been minimized.
perf: store the fulfillment engine inline in ObligationCtxt
This comment has been minimized.
This comment has been minimized.
|
Finished benchmarking commit (b4c312d): comparison URL. Overall result: ✅ improvements - no action neededBenchmarking means the PR may be perf-sensitive. It's automatically marked not fit for rolling up. Overriding is possible but disadvised: it risks changing compiler perf. @bors rollup=never rustc-perf Instruction countOur most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.
Max RSS (memory usage)Results (primary 0.4%, secondary 0.7%)A less reliable metric. May be of interest, but not used to determine the overall result above.
CyclesResults (primary -1.0%, secondary -0.7%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Binary sizeThis perf run didn't have relevant results for this metric. Bootstrap: 490.376s -> 490.923s (0.11%) |
|
r? @nnethercote |
There was a problem hiding this comment.
Interesting. #155714 removed the box from both ObligationCtxt::engine and TypeckRootCtxt::fulfilllment_cx. This PR only does the former. This PR's performance wins are slightly smaller but it also avoids the regressions that #155714 had. Looks good to me.
Is this still meant to be marked as a draft?
|
Some changes occurred in engine.rs, potentially modifying the public API of cc @lcnr |
|
Thanks for the quick review! Should be ready. After seeing your PR I tested a bit and I found a similar regression when I added some of the |
|
If inlining was the problem in #155714, is it worth trying to de-box |
Every
ObligationCtxtallocated its fulfillment engine on the heap as aBox<dyn TraitEngine>. This was the single largest allocation site in the compiler: 161k allocations on asyncheck build (measured with DHAT).ObligationCtxts are created in hot paths, for example once per candidate probe during method resolution.The allocation is easy to avoid. Which solver is used never changes during a compilation session, and both engine types are small (the obligation forest allocates its own storage separately). So this PR stores the engine directly inside
ObligationCtxt, in a two-variant enum. Calls now go through a match on that enum instead of virtual dispatch.The enum's
TraitEngineimpl needs bothFromSolverErrorbounds, so a few generic impl blocks and two generic users now need both bounds as well. The concrete error types used in practice already implement both, so nothing else changes for callers. The typeck root fulfillment context keeps the boxed engine; it is created once per function body, so the allocation does not matter there.