This project explores and compares a variety of parallel and serial strategies for initializing a large Vec<u64> in Rust. It measures how different concurrency models and parallelization frameworks (Tokio and Rayon) affect performance, scalability, and cache locality relative to a single-threaded baseline.
TL;DR:
- For very simple initial value computation functions (constants, simple arithmetic), you need on the order of 10^6 (i.e. millions) of items before parallel initialization makes sense.
- For more complex initial value computation functions or very large vectors,
tokio_static_chunksor any of therayonwork-stealing algorithms perform in the same order of magnitude amount of time and are reasonable choices.
Each benchmark fills a vector of size N = 1_000, 100_000, or 1_000_000, with computed values derived from a compute_value function. There are two versions of compute_value, a "simple" version that performs a simple arithmetic opertation, and a "complex" version that combines data lookup, per-thread RNG, and arithmetic.
| # | Benchmark Name | Framework | Parallelism Model | Chunking Strategy | Work Distribution | Notes |
|---|---|---|---|---|---|---|
| 1 | serial_baseline | None | Single-threaded | None | Sequential | Reference baseline; ideal cache locality, no parallelism |
| 2 | tokio_static_chunks | Tokio | Multi-threaded (blocking pool) | Static, one chunk per CPU | Deterministic | Low overhead; excellent for uniform workloads |
| 3 | tokio_dynamic_chunks | Tokio | Multi-threaded (blocking pool) | Dynamic, ~8× chunks per CPU | Semi-dynamic | Better load balance for uneven workloads |
| 4 | tokio_per_element * | Tokio | Fine-grained | None (1 task per element) | Fully dynamic | Extreme stress test for scheduling overhead |
| 5 | tokio_auto_chunks | Tokio | Multi-threaded (blocking pool) | Fixed ~10 000 elements per task | Deterministic | Practical balance between granularity and overhead |
| 6 | rayon_par_range | Rayon | Work stealing | Implicit adaptive ranges | Dynamic | Automatically balanced, concise API |
| 7 | rayon_par_iter_mut | Rayon | Work stealing | Automatic adaptive slices | Dynamic | Efficient preallocated parallel fill |
| 8 | rayon_par_chunks | Rayon | Work stealing | Explicit fixed-size slices | Semi-static | Tunable chunk size; similar to static Tokio version |
* The tokio_per_element variant is included for completeness but commented out by default due to its extremely high overhead.
Run all benchmarks:
cargo bench
Run a specific benchmark:
cargo bench -- tokio_static_chunks
Benchmark results will be written under target/criterion/, including interactive HTML reports.
Results as measured on my laptop in microseconds (μs). Fastest result is bold, second fastest is italic.
| Complex Initializer | Simple Initializer | |||||
|---|---|---|---|---|---|---|
| (Algorithm, N) | 1000 | 100,000 | 1,000,000 | 10,000 | 100,000 | 1,000,000 |
| serial_baseline | 16.352 | 2212.4 | 18118 | 2.5622 | 25.553 | 584.00 |
| tokio_static_chunks | 44.443 | 455.49 | 4541.2 | 40.856 | 49.742 | 495.47 |
| tokio_dynamic_chunks | 385.63 | 604.27 | 4180.2 | 384.3 | 382.21 | 772.52 |
| tokio_auto_chunks | 6584.4 | 62748 | 101150 | 96166 | 95723 | 65704 |
| rayon_par_range | 49.734 | 488.79 | 4414.6 | 50.915 | 74042 | 300.62 |
| rayon_par_iter_mut | 48.962 | 574.33 | 5465.2 | 43.42 | 69.25 | 515.83 |
| rayon_par_chunks | 30.057 | 536.28 | 4986.5 | 21.361 | 40.97 | 540.25 |
Copyright © 2025 Robert Jacobson. This software is distributed under the terms of the MIT license or the Apache 2.0 license at your preference.