Add integer-bench: a standalone bigint comparison benchmark crate - #65
Add integer-bench: a standalone bigint comparison benchmark crate#65DRMacIver wants to merge 1 commit into
Conversation
Adds `integer-bench`, a separate crate (excluded from the workspace) that benchmarks dashu-int against other bigint libraries with criterion. Keeping it out of the workspace means dashu-int's `--all-features` builds and MSRV check never pull in the comparison libraries, and the bench crate can carry its own newer toolchain requirement (malachite needs Rust 1.90). Each criterion bench body is written once, generic over a `Backend` (see integer-bench/src/lib.rs), and run for every backend with the backend name as a `BenchmarkId` dimension — so one `cargo bench` run reports all libraries side-by-side in one group. This revives the trait-based, multi-library approach of the top-level `benchmark/` harness while emitting criterion measurements. Backends: dashu, ibig, num-bigint and malachite are always built (pure Rust); rug (GNU GMP) is added under the `gmp` feature. Every backend samples by drawing a dashu value and converting it, so magnitudes line up point-for-point across libraries. The abstraction: * `BenchInt` — by-ref ops shared by the unsigned and signed types. Several libraries' by-ref operators return lazy incomplete values, so each op is a method (finalised to an owned value) rather than a std `Add`/`Sub`/... bound. The `*_assign` family has portable defaults; backends with a native `+=` override. * `UnsignedInt` / `SignedInt` — primitive constructors / `+=` / `TryInto<i128>`. dashu/num/malachite/ibig have a `UBig`/`IBig`-style split; rug uses one signed type for both. * `Backend` — picks the types + samplers, plus the `magnitude` (`unsigned_abs`/`abs`) and `unsigned_to_signed` bridges. * `PrimitiveInt` / `PrimitiveBackend` — the bit-width sweep's extra surface (gcd, extended-gcd, pow, radix; each backend uses its native routine) plus modular arithmetic. The modular ops are like-for-like: `mod_mul` is plain multiply-then-reduce and `mod_pow` is each library's native one-shot modpow, with nothing precomputed. Benches: `primitive` (bit-width sweep up to 10^4 bits), `small_int`, `workload`, `shrinker`. The bit-width sweep stops at 10^4 bits — enough to show the crossover where GMP-backed libraries pull ahead, without the very large sizes. The workload/shrinker benches model a property-based-testing generator and shrinker; their shapes were drawn from profiling hegel-rust (https://github.com/DRMacIver/hegel) but are written to stand on their own. CI gets a `smoke-test-integer-bench` job that builds the benches and runs each once via criterion `--test` (no measurement, no numbers reported, since CI is too noisy for real benchmarking), so the crate can't silently rot.
|
Thanks for putting up this benchmark! Unfortunately I don't plan to include a whole cross-comparison benchmark inside this repo. The benchmark in this repo is all for profiling. Apart from this, it's also greater for your benchmark to be independent, so that the results are more reproducible and creditable (like tczajka's https://github.com/tczajka/bigint-benchmark-rs) |
|
Besides, I acknowledge that the library is falling short in large number mul/div, since I don't have a o(nlogn) multiplication algorithm implemented (which malachite has). It's in plan, but it takes time. (help on this is welcome :P) |
Sure thing, completely reasonable! I've put it up as a standalone repo on my own github. https://github.com/DRMacIver/rust-bigint-benchmarks
Yup, understood. These benchmarks mostly weren't about that - they're about the small-integer regime where dashu already shines (because this was a lot of the regime where my workloads were heavy in, which is a lot of why I chose dashu in the first place!). The upper end is to show the crossover point, not because I think they're highlighting a serious deficiency in dashu.
More than happy to do this if you'd like. I was already considering it (it's not my main workload in hegel, but it does come up in places). It will be almost entirely LLM-generated work, but I'm confident that between testing against strong oracles and my own code review it can be high quality. |
|
@DRMacIver Thanks for your proposal! Let me try myself (It's also a case where I can learn more about agentic coding haha), if I failed I will get back to you for help (:P) |
|
Sure thing! Good luck, and let me know if you'd like any advice / assistance. |
I mentioned I've been doing a bunch of perf work on dashu to try to optimise it for some of our workloads in hegel. Here's the set of benchmarks I've been developing against. I've added a bunch of comparisons to other libraries in it, though those are more because I thought it would illuminating about where there are gains to be had than anything else. My actual use case is to validate various performance improvements on dashu.
First off: Apologies, this is an unreasonable amount of code. I can break it up into multiple pull requests, but I'm not sure that would help much. Feel free to reject it if you'd rather not have this much novel benchmarking code in your library, and if so I'll publish it in a separate repo somewhere instead, but I found it super useful for guiding my perf work and wanted to offer it, and it will be useful for making sense of my upcoming patches.
These are generally focused on integer operations in the "small" integer regime of < 10^4 bits. This makes them look quite flattering to dashu - in larger regions than that, rug and malachite's gmp (and gmp-derived) based algorithms win big and dashu starts to suffer - because those are the regimes I cared most about for my use case. You can start to see this transition in the 10^3 to 10^4 bit region.
I've added these as a separate benchmarking crate (not published) as I didn't want to add features to the main dashu crate (especially ones that would raise the MSRV) and didn't want to interfere with your existing benchmarks. CI gets a
smoke-test-integer-benchjob that builds the benches and runs each once via criterion--test(no measurement, no numbers reported, since CI is too noisy for real benchmarking), this is just to make sure the benchmarks don't rot.Here are my local measurements of these benchmarks. You shouldn't consider these too authoritative. They come from a criterion run with a short measurement time (1 s warm-up, 2 s measurement, 100 samples) on my macbook (which wasn't otherwise under heavy load, but certainly wasn't perfectly clean).
Summary
ubig_mulat 10⁴ bits: rug ~4.5 µs vs dashu ~12.8 µs), with malachite usually second.Detailed run results follow:
These were run on an Apple M2 Max, macOS (Darwin 25.5.0, arm64) with rustc 1.92.0 (ded5c06cf 2025-12-08).
I used dashu-int 0.4.2 (path), ibig 0.3.6, num-bigint 0.4.6, malachite-nz 0.9.1, rug 1.30.0 (GMP 6.x).
Criterion settings:
--warm-up-time 1 --measurement-time 2 --sample-size 100.Each cell is the median wall-clock time; bold marks the fastest library in that row.
Details
## primitive (bit-width sweep)ubig_addubig_sububig_mulubig_divubig_gcdubig_gcd_extubig_to_hexubig_to_decubig_from_hexubig_from_decubig_powubig_modulo_mulubig_modulo_powsmall_int (small / inline-magnitude values)
ibig_from_i64ibig_from_i128ubig_from_u64ubig_from_u128ibig_try_into_i128ubig_add_by_classubig_mul_by_classibig_add_by_classubig_add_mixedubig_add_assign_by_classibig_add_assign_by_classubig_sub_assign_by_classibig_sub_assign_by_classubig_add_assign_heap_acc_small_rhsibig_add_assign_heap_acc_small_rhsibig_add_assign_i64_into_heap_accibig_add_assign_i128_into_heap_accubig_add_assign_u64_into_heap_accubig_add_assign_u128_into_heap_accubig_bitxor_assign_by_classubig_equbig_cmpubig_hashubig_cloneibig_display_smallibig_from_str_smallworkload (generator/shrinker scenarios)
running_sum_and_comparerunning_sum_and_compare_smallrunning_sum_and_compare_under_1kbitstring_round_tripstring_round_trip_under_1kbitbounded_arithmetic_mixbounded_arithmetic_mix_smallbounded_arithmetic_mix_under_1kbitshrinker (property-based-testing shrinker operations)
ibig_clonechoice_node_cloneibig_dropibig_sub_magnitudeibig_clampibig_double_cmpibig_from_constubig_cmp_shrinkeribig_shr_descentshrinker_considerubig_binary_search_stepibig_hashmap_keysfrom_index_full_searchubig_ref_addubig_ref_subibig_boundary_sortubig_mininteger_choice_to_indexnodes_sort_key_lex_cmpshrinker_descent_subtract