tlopex commented on PR #806: URL: https://github.com/apache/tvm-ffi/pull/806#issuecomment-5755851378
Benchmark comparison between C++ and Rust. Measured on an AMD Threadripper 3970X, pinned to one CPU core. Rust uses 1.97.1 with release optimization and thin LTO; C++ uses Clang 18.1.3 with `-O3`. Both use the same native runtime. Results are medians of seven measurements, reported per complete traversal; lower is better. Input construction, warmup, and output validation are outside the timer. For an array of **4,096 integers**, with no additional owner retained, times are in **µs/traversal**: | Operation | C++ | Rust | | --- | ---: | ---: | | Visit | 12.50 | 9.26 | | Walk, pre-order | 41.41 | 8.97 | | Walk, post-order | 42.11 | 8.87 | | Map, pre-order, increment integers | 95.24 | 13.37 | | Map, post-order, increment integers | 134.55 | 13.41 | | Mutate, increment integers | 27.83 | 9.87 | | Mutate, return `Unchanged` | 11.56 | 9.41 | | Walk, pre-order + context policy | 42.20 | 31.32 | | Mutate, increment integers + context policy | 27.73 | 9.89 | What these operations measure: - **Visit:** integer callbacks record values and counts; unmatched containers use default recursion. Visit callbacks can control recursion themselves. - **Walk:** the engine schedules callbacks before children (pre-order) or after children (post-order). These callbacks only collect statistics. - **Map:** the engine schedules callbacks and recursion; each integer callback returns `value + 1`. Both traversal orders produce the same values in this workload. - **Mutate:** callbacks control rewriting and can request default recursion. Here, integer callbacks return `value + 1`, while unmatched containers descend automatically. Uniquely owned arrays are eligible for in-place reuse. - **`Unchanged`:** integer callbacks still run but return the no-replacement marker. This measures traversal and result handling when nothing changes. - **Context policy:** increments shared depth before default descent and restores it afterward; callbacks also record depth. It adds context management without changing the rewrite rules. Other input shapes and ownership cases, also in **µs/traversal**. These rows have **no context policy**: | Workload | C++ | Rust | | --- | ---: | ---: | | Shared 4,096-element array, mutate | 61.22 | 66.59 | | Reflected binary tree, mutate | 105.83 | 109.40 | | Reflected binary tree, closure callbacks returning `Unchanged` | 71.98 | 80.33 | | Shared-node DAG, mutate | 180.34 | 200.32 | - **Shared array:** retain another owning reference during each traversal, exercising copying instead of modifying the shared input in place. - **Reflected binary tree:** 1,023 objects with registered `left`/`right` fields and 1,024 integer leaves, exercising field reflection. The closure row uses Rust closure callbacks; the other rows above use macro-generated Rust dispatchers. - **Shared-node DAG:** an array holds 4,096 references to 512 distinct DAG objects, each referenced eight times. Each object has an integer field. This exercises identity remapping and repeated references. Changed cases increment integers by one. The larger workload simplifies **128 index expressions with four nested div/mod expansion levels**. It includes rules such as `(x // 8) * 8 + x % 8 → x`, nested division folding, constant folding, and removing `+ 0` / `* 1`. Times are in **ms/traversal**: | Operation | C++ | Rust | | --- | ---: | ---: | | Map simplification | 3.157 | 2.408 | | Map simplification + context policy | 3.169 | 3.963 | | Mutate simplification | 3.056 | 3.162 | | Mutate simplification + context policy | 3.053 | 4.155 | Map uses post-order callbacks. Mutate callbacks explicitly descend first, then apply the same rewrite rules. The expression policy tracks binary-expression nesting. Validation checks eight variable assignments, the resulting expression structure, and callback/rewrite counters. The complete run covered **186 configurations and 4,158 measurements**, including smaller/nested arrays, closures, shared expression DAGs, and patterns that should not simplify. Output checks and callback/state counters matched across implementations. The measurement total includes an additional GCC comparison for expression cases; the tables above use Clang. These are local workload results on a shared machine. Rust is faster in the array cases without an additional owner, while shared/reflected mutation remains slower. Expression mutation with a policy takes about 36% longer than C++. -- 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]
