March Docs

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 count

Convenience wrappers built on preduce: psum, psum_float, pcount, pany, pall

All functions follow the same structure:

  1. chunk(input, workers) — split into N slices
  2. task_spawn per slice — each worker transforms its slice
  3. 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 squares

See docs/cookbook/parallel-data.md for full examples.

Functions

fnpallpall(v, pred): Bool#

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)
    true
fnpanypany(v, pred): Bool#

True 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)
    true
fnpcountpcount(v, pred): Int#

Count elements satisfying pred in parallel.

Examples

    march> Parallel.pcount(RRB.range(1, 11), fn n -> n % 2 == 0)
    5
fnpmappmap(input, f)#

Map 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))
fnpmap_npmap_n(input, f, workers: Int)#

Like pmap but with an explicit workers count. Passing a larger value creates finer chunks for heterogeneous per-element workloads.

fnpreducepreduce(input, zero, f, merge)#

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)
fnpreduce_npreduce_n(input, zero, f, merge, workers: Int)#

Like preduce but with an explicit workers count.

fnpsumpsum(v): Int#

Parallel sum of an integer Vec.

Examples

    march> Parallel.psum(RRB.range(1, 6))
    15
fnpsum_floatpsum_float(v): Float#

Parallel 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.