tlopex commented on PR #692: URL: https://github.com/apache/tvm-ffi/pull/692#issuecomment-5137498917
Here is the benchmark: All measurements use an x86-64 pinned CPU, O3 builds, preconstructed objects, and 31 rotated samples. T1 uses three shuffled seeds; the table reports the median across those seeds. The benchmark exercises the generated `match_any!` code directly. 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 `Err(())`, without constructing a diagnostic error. | Conversion workload | Rust | C++ `ObjectRef::as<T>()` | | --- | ---: | ---: | | One type, match | 8.428 ns | 9.246 ns | | One type, reject | 1.393 ns | 1.153 ns | | Two types, first matches | 8.438 ns | 9.510 ns | | Two types, second matches | 8.398 ns | 10.939 ns | | Two types, neither matches | 1.624 ns | 0.934 ns | The lightweight Rust conversion is therefore in the same expected range as C++. ### T1: 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. | Arms | Production path | Hits: Ordered | Selected path | Improvement | All outcomes: Ordered | Selected path | Improvement | | ---: | --- | ---: | ---: | ---: | ---: | ---: | ---: | | 8 | Ordered | 8.533 ns | 8.533 ns | retained | 8.731 ns | 8.734 ns | retained | | 12 | Ordered | 9.817 ns | 9.813 ns | retained | 9.976 ns | 9.968 ns | retained | | 16 | Ordered | 10.713 ns | 10.712 ns | retained | 10.788 ns | 10.787 ns | retained | | 20 | Direct | 12.355 ns | 11.665 ns | 5.6% | 12.686 ns | 11.256 ns | 11.3% | | 32 | Direct | 19.461 ns | 11.928 ns | 38.7% | 20.039 ns | 11.629 ns | 42.0% | | 48 | Direct | 30.866 ns | 12.054 ns | 60.9% | 31.501 ns | 11.917 ns | 62.2% | | 64 | Direct | 44.733 ns | 12.091 ns | 73.0% | 45.458 ns | 11.988 ns | 73.6% | 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. ### T0: repeated 20-arm input | Repeated outcome | Ordered | Direct | Direct change | | --- | ---: | ---: | ---: | | First arm | 8.363 ns | 8.625 ns | 3.1% slower | | Middle arm | 7.830 ns | 8.638 ns | 10.3% slower | | Last arm | 16.203 ns | 8.644 ns | 46.7% faster | | Fallback | 13.260 ns | 1.630 ns | 87.7% faster | 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 call The type metadata and objects are warm, but the call-site `OnceLock` is intentionally uninitialized. | First 20-arm outcome | Ordered | Direct | | --- | ---: | ---: | | Last-arm match | 290 ns | 441 ns | | Fallback | 270 ns | 411 ns | Direct lookup pays approximately 140–150 ns once to build the call-site table. Subsequent calls use the steady-state path measured above. ### Optimized assembly The O3 assembly confirms that the conversion and lookup helpers are inlined. After the `OnceLock` and pattern-list checks, the hot lookup performs: `runtime TypeIndex → subtract base → bounds check → u32 ArmId load → jump to selected arm` The selected conversion then proceeds directly to the expected reference-count operations without repeating the runtime type check. | Path | Instructions executed per match | Conditional branches per match | Compiled function size | | --- | ---: | ---: | ---: | | 20-arm ordered | 79.9 | 27.0 | 3,883 bytes | | 20-arm direct | 46.2 | 10.0 | 5,111 bytes | | 64-arm ordered | 248.4 | 71.0 | 5,863 bytes | | 64-arm direct | 48.3 | 11.1 | 12,949 bytes | 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. Based on these results, 20 arms remains a conservative threshold: small and predictable matches stay ordered, while larger mixed-type matches receive the intended O(1) dispatch benefit. Benchmark-only code is not included in the PR. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
