[Perf][Rust] Optimize match_any! ordered conversion and exact-leaf dispatch - #692
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
e2fb57b to
3f859e0
Compare
|
Here is the result of the benchmark: A successful conversion checks the runtime type and constructs and drops the target handle, including the reference-count increment and decrement. A rejected conversion only checks the runtime type and returns
The lightweight Rust conversion is therefore in the same expected range as C++. Shuffled runtime types“Hits” is uniform across all typed arms. “All outcomes” is uniform across the typed arms plus one fallback outcome. Below 20 arms, the production macro keeps ordered dispatch; from 20 exact-leaf arms, it uses direct lookup.
Ordered dispatch grows with the number of preceding arms. Direct dispatch stays around 12 ns from 20 through 64 arms, showing the intended O(1) behavior. Repeated 20-arm input
This shows the expected tradeoff: a perfectly predictable early ordered arm can remain slightly faster, while later arms and misses benefit substantially from direct lookup. First eligible callThe type metadata and objects are warm, but the call-site
Direct lookup pays approximately 140–150 ns once to build the call-site table. Subsequent calls use the steady-state path measured above. Optimized assemblyThe O3 assembly confirms that the conversion and lookup helpers are inlined. After the
The selected conversion then proceeds directly to the expected reference-count operations without repeating the runtime type check.
These instruction counts come from Callgrind and are simulated rather than hardware-counter measurements. Direct lookup generates more code because the initialization path, fallback, jump table, and all typed handlers remain at the call site, but it executes nearly constant work as the number of arms increases. |
This PR improves both Rust
match_any!dispatch paths without changing how matching works.Ordered dispatch
When an arm does not match, the internal conversion now returns
Err(())instead of creating aTypeError. This makes failed checks cheaper while keeping the publicTryFrom<AnyView>API unchanged.Custom
TryIntomatchers are still supported and continue to run in source order.Exact-leaf dispatch
For a match with many exact final object types, the macro builds one
TypeIndex → ArmIdtable throughOnceLock.The table uses the smallest pattern
TypeIndexas its starting point:Each call then selects an arm directly with:
This is an O(1) lookup. Only the selected object handle is created, and its type check is not repeated.
The direct table is used when there are at least 20 typed arms, every pattern matches one exact final runtime type, there are no guards, and bindings are simple names or
_. Smaller matches, guarded arms, parent-type patterns, parameterized containers, and custom matchers continue using ordered dispatch.The input value is still evaluated once. Duplicate types still select the first arm, and unmatched or non-object values still use the final
_fallback. ATypeIdcheck prevents the same static table from being incorrectly shared by different generic pattern lists.