Parallel
Parallel map-reduce over RRB.Vec.
Three public entry points:
pmap (input, f) -- transform every element, collect in order
preduce (input, zero, f, g) -- transform + reduce with explicit zero/merge
pmap_n (input, f, workers) -- like pmap but with explicit worker count
preduce_n(input, zero, f, g, workers) -- like preduce with explicit countConvenience wrappers built on preduce: psum, psum_float, pcount, pany, pall
All functions follow the same structure:
- chunk(input, workers) — split into N slices
- task_spawn per slice — each worker transforms its slice
- fold_left over tasks — await and merge results left-to-right
Static partitioning means each worker processes a fixed slice. Imbalanced workloads (elements that vary widely in processing time) may leave fast workers idle while slow ones finish. This is a known v1 limitation.
Interpreter vs. compiled
Tasks run eagerly (single-threaded) in the interpreter, giving correct but sequential results. Real multi-core parallelism requires compiling with forge build or march --compile.
Quick start
let nums = RRB.range(1, 1_000_001)
let total = Parallel.psum(nums) -- 500_000_500_000
let evens = Parallel.pcount(nums, fn n -> n % 2 == 0) -- 500_000
let sq = Parallel.pmap(nums, fn n -> n * n) -- Vec of squaresSee docs/cookbook/parallel-data.md for full examples.
Functions
True if pred holds for every element. Evaluates all chunks (no early exit in v1).
Examples
march> Parallel.pall(RRB.from_list([2, 4, 6]), fn n -> n % 2 == 0)
trueTrue if pred holds for at least one element. Evaluates all chunks even after finding a match (no short-circuit in v1).
Examples
march> Parallel.pany(RRB.from_list([1, 3, 4, 7]), fn n -> n % 2 == 0)
trueCount elements satisfying pred in parallel.
Examples
march> Parallel.pcount(RRB.range(1, 11), fn n -> n % 2 == 0)
5Map f over every element of input in parallel, collecting results in a new Vec in the same order as the input. Uses System.cpu_count() workers. For a custom worker count, use pmap_n.
Examples
let doubled = Parallel.pmap(RRB.range(1, 6), fn n -> n * 2)
-- Vec [2, 4, 6, 8, 10], same order as input
let rows = Parallel.pmap(records, fn r -> render(r))Like pmap but with an explicit workers count. Passing a larger value creates finer chunks for heterogeneous per-element workloads.
Map f over every element then reduce with merge. Uses zero as the starting accumulator for each chunk (it must be the identity for merge). Uses System.cpu_count() workers. For a custom count, use preduce_n.
**Contract:** `merge` must be associative and `zero` must be its identity.
**Reproducibility note:** the input is split into `System.cpu_count()` chunks,
so the *grouping* of `merge` applications depends on the backend's CPU/domain
count. If `merge` is only approximately associative (e.g. floating-point `+.`),
the result is not bit-identical across backends — do not golden-compare such a
`preduce`. Integer merges (`+`, `max`, `||`) are exactly associative and are
reproducible.
## Examples
-- Parallel sum of squares
let sum_sq = Parallel.preduce(RRB.range(1, 1001), 0, fn n -> n * n, fn (a, b) -> a + b)
-- Parallel max (identity = Int.min_value)
let mx = Parallel.preduce(scores, -2147483648, fn n -> n, fn (a, b) -> if a > b do a else b end)Like preduce but with an explicit workers count.
Parallel sum of an integer Vec.
Examples
march> Parallel.psum(RRB.range(1, 6))
15Parallel sum of a float Vec.
**Not bit-reproducible across backends.** Floating-point addition is not
associative, and the number of parallel chunks depends on the worker count,
which differs between the interpreter (`Domain.recommended_domain_count`) and
the compiled runtime (physical CPU count). The same input can therefore sum to
slightly different values on different backends — do **not** golden-compare the
result of `psum_float` (or any `preduce` with a non-associative merge).
## Examples
march> Parallel.psum_float(RRB.from_list([1.0, 2.5, 3.5]))
7.