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]

Reply via email to