rust 92 lines · 2 tabs

Partition a Slice of Orders into Shards for Parallel Processing with Rayon

Shared by codesnips Aug 2026
2 tabs
#[derive(Debug, Clone)]
pub struct Order {
    pub id: u64,
    pub amount_cents: i64,
    pub currency: [u8; 3],
}

pub fn shard_indices(len: usize, shards: usize) -> Vec<(usize, usize)> {
    if len == 0 || shards == 0 {
        return Vec::new();
    }
    let shards = shards.min(len);
    let base = len / shards;
    let rem = len % shards;

    let mut ranges = Vec::with_capacity(shards);
    let mut start = 0usize;
    for i in 0..shards {
        let extra = if i < rem { 1 } else { 0 };
        let end = start + base + extra;
        ranges.push((start, end));
        start = end;
    }
    ranges
}

pub fn into_shards<'a>(orders: &'a [Order], shards: usize) -> Vec<&'a [Order]> {
    shard_indices(orders.len(), shards)
        .into_iter()
        .map(|(start, end)| &orders[start..end])
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn empty_slice_has_no_shards() {
        assert!(shard_indices(0, 4).is_empty());
    }

    #[test]
    fn uneven_split_is_balanced() {
        let ranges = shard_indices(10, 3);
        let sizes: Vec<usize> = ranges.iter().map(|(s, e)| e - s).collect();
        assert_eq!(sizes, vec![4, 3, 3]);
        assert_eq!(ranges.last().unwrap().1, 10);
    }
}
2 files · rust Explain with highlit

This snippet shows how a batch of orders is split into balanced shards and processed across worker threads, a common pattern when a single large collection must be crunched in parallel without oversaturating the thread pool. The sharding module in the first tab implements the core split logic, while the pipeline module in the second tab consumes those shards with Rayon and aggregates the per-shard results.

In sharding.rs, shard_indices computes contiguous slice boundaries for a given length and shard count using integer division and a remainder. The remainder is spread across the first rem shards so that shard sizes differ by at most one element; this beats naive len / n chunking, which leaves an awkward oversized or undersized final chunk and skews load. The function guards against shards == 0 and against shards exceeding len, returning at most one shard per element so no empty shards are produced. into_shards then borrows the input slice and yields &[Order] subslices rather than cloning, keeping the partition zero-copy.

Contiguous ranges are chosen deliberately: they preserve locality, work with any Copy-free payload, and let each shard be handed to a thread as a plain borrow. The trade-off is that if per-order cost varies wildly, equal-sized shards can still be unbalanced in wall-clock terms; for that case work-stealing or a smaller shard size is preferable, which is exactly what Rayon's dynamic scheduling helps with.

In pipeline.rs, process_orders calls into_shards and drives the parallel pass. Using par_iter over the shard vector, each shard is reduced independently by summarize_shard, and reduce folds the ShardSummary values into one total with merge. Because each shard is a disjoint borrow, there is no shared mutable state and therefore no locking on the hot path. ShardSummary::merge is associative, which is what makes the parallel reduce correct regardless of how Rayon splits the work.

The #[cfg(test)] block pins the boundary behavior: an empty slice yields nothing, and a length that does not divide evenly still produces near-equal shards. Choosing shard count near the CPU count, then letting Rayon steal, is the usual sweet spot for throughput.


Related snips

Share this code

Here's the card — post it anywhere.

Partition a Slice of Orders into Shards for Parallel Processing with Rayon — share card
Link copied