From 87b49bc5c75af6d39f9a7f192fa310d69c944000 Mon Sep 17 00:00:00 2001 From: Morsel Date: Thu, 22 Oct 2026 22:27:20 +0000 Subject: [PATCH] docs(pmap): state the sequential callback contract --- docs/docs/architecture/pipeline.md | 6 ++++-- docs/docs/guides/analytics.md | 11 ++++++----- docs/docs/language/functions.md | 2 +- docs/docs/language/math.md | 16 ++++++++++++++-- docs/docs/reference/all-functions.md | 4 ++-- 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/docs/docs/architecture/pipeline.md b/docs/docs/architecture/pipeline.md index 7e68d4b8c..0ca8aba2d 100644 --- a/docs/docs/architecture/pipeline.md +++ b/docs/docs/architecture/pipeline.md @@ -169,7 +169,7 @@ Element-wise operation subtrees are compiled to flat bytecode at **execution tim - **Registers**: up to 16 slots (`EXPR_MAX_REGS`), each holding a typed lane — `REG_SCAN` (column slice), `REG_CONST` (broadcast scalar), or `REG_SCRATCH` (computed intermediate). - **Instructions**: up to 48 opcodes (`EXPR_MAX_INS`) covering arithmetic, comparisons, casts, and logical operations. - **Morsel evaluation**: the compiled program is evaluated one morsel at a time (1024 rows) in per-worker, arena-allocated scratch buffers. No full-length intermediate vectors are materialized. -- **Parallelism**: subtrees covering ≥ 65,536 rows (`RAY_PARALLEL_THRESHOLD`) are dispatched across worker threads, each thread processing its own morsel slice independently. +- **Parallelism**: eligible compiled subtrees can dispatch data-parallel ranges across worker threads, each processing its own morsel slice. A common minimum is 65,536 rows (`RAY_PARALLEL_THRESHOLD`); the chosen operator, available workers and dispatch-safety guards also determine whether work runs in parallel. This does not parallelize arbitrary Rayfall callbacks. - **Parted-segment aware**: for partitioned tables, column pointers are rebound per segment at evaluation time — zero-copy, no gather. **Null handling.** Nullable integer columns are sentinel-normalized at morsel load: `NULL_I32`/`NULL_I16` are widened to `NULL_I64` in `i64` lanes, or to `NaN` in `f64` lanes. Per-instruction null-aware kernel variants are selected at compile time (sentinel-checking `i64` arithmetic and comparisons, sentinel-mapping casts). F64 `NaN`-nulls propagate through arithmetic natively. Output vectors carry `RAY_ATTR_HAS_NULLS` conservatively. Null-free programs compile to the identical instruction stream as non-nullable inputs — no null checks on the fast path. @@ -203,7 +203,9 @@ This three-tier scheme means that fully-passing or fully-failing morsels (which ### Thread Pool -Rayforce uses a global thread pool (`ray_pool_t`) for parallel execution. Queries with more than `RAY_PARALLEL_THRESHOLD` (64 * 1024 = 65,536) elements are dispatched across worker threads. Each thread processes `RAY_DISPATCH_MORSELS` (8) morsels at a time to amortize scheduling overhead. +Rayforce uses a global thread pool (`ray_pool_t`) for eligible vector builtins and query operators. Many kernels use `RAY_PARALLEL_THRESHOLD` (64 * 1024 = 65,536) elements as a minimum and batch `RAY_DISPATCH_MORSELS` (8) morsels to amortize scheduling overhead. Eligibility and dispatch geometry are operator-specific; worker availability and safety guards (including avoiding nested dispatch) can keep work serial even above a size threshold. + +This is **kernel parallelism**, not automatic parallel evaluation of language functions. `map` and `pmap` currently invoke their callbacks sequentially on the caller's VM; `pmap` is an alias for `map`. A builtin or query inside a callback may itself use eligible parallel kernels. Increasing `-c` does not turn an interpreted parameter sweep into concurrent lambda calls. See [Higher-order functions](../language/math.md#pmap-f-x) and [Multiple Instances](../guides/instances.md). Each dispatch splits its tasks into one contiguous range per worker (the main thread included); a worker runs its range in order and, once it is empty, steals half of another worker's remaining range from the tail, so an idle thread never waits on a busy one and the rows a worker touches are sequential. Tasks of one dispatch may therefore run in any order and on any worker: operators place results by task index, and any early stop (a `take:` cutoff, a top-N bound) is derived from row positions or values, never from "the earlier tasks have run". The scheduler is selected once at pool creation (`RAY_POOL_STEAL`, default `1`; `0` restores the shared claim cursor for A/B comparison) and `RAY_POOL_TRACE=1` prints the own-versus-stolen task histogram per worker to stderr at pool destroy; see [REPL Reference](../language/repl.md#environment-variables). diff --git a/docs/docs/guides/analytics.md b/docs/docs/guides/analytics.md index 2f891f218..bd117dfc2 100644 --- a/docs/docs/guides/analytics.md +++ b/docs/docs/guides/analytics.md @@ -347,15 +347,16 @@ Null literals are type-suffixed: `0Nl` (i64), `0Nf` (f64), `0Nd` (date), `0Nt` ( - **Prefer `select` with `by:`** over manual group-then-map. The `select` pipeline fuses grouping and aggregation into a single pass, avoiding intermediate materialization. - **Use `xbar` for time bucketing** to bin timestamps into fixed intervals (e.g., 5-minute bars). This avoids materializing group keys and is faster than building date groups by hand. - - **Use `pmap` for embarrassingly parallel work.** It distributes a function across list elements using worker threads. Ideal for independent per-group transformations: + - **Express column work with vector builtins and queries.** Eligible kernels can use the morsel-driven pool; scheduling depends on the operator, input size and available workers. For example, squaring a vector does not require a callback per element: ```lisp - (pmap (fn [x] (* x x)) (list 1 2 3 4 5)) + (set x [1 2 3 4 5]) + (* x x) ; [1 4 9 16 25] ``` - ```text - (1 4 9 16 25) - ``` + This small example may run serially. See [Thread Pool](../architecture/pipeline.md#thread-pool) for the distinction between kernel parallelism and language evaluation. + + - **Do not use `pmap` as a parallel task scheduler.** It is currently a sequential alias for `map`, including for large inputs and expensive lambdas. Builtins inside a callback may use workers independently, but the callback invocations themselves remain sequential. For independent backtest configurations or other Rayfall task sweeps, use separate processes with an explicit per-process worker budget; see [Multiple Instances](instances.md). ## Next Steps diff --git a/docs/docs/language/functions.md b/docs/docs/language/functions.md index 6962ec08c..76ed528f5 100644 --- a/docs/docs/language/functions.md +++ b/docs/docs/language/functions.md @@ -129,7 +129,7 @@ Functions that take other functions as arguments. | Function | Type | Description | Example | |---|---|---|---| | `map` | variadic | Apply function to each element (returns a list) | `(map (fn [x] (* x 2)) [1 2 3])` → `(2 4 6)` | -| `pmap` | variadic | Parallel map (multi-threaded, returns a list) | `(pmap (fn [x] (* x x)) [1 2 3])` → `(1 4 9)` | +| `pmap` | variadic | Currently a sequential alias for `map` (returns a list) | `(pmap (fn [x] (* x x)) [1 2 3])` → `(1 4 9)` | | `filter` | binary | Keep elements where boolean mask is true | `(filter [1 2 3 4] (> [1 2 3 4] 2))` → `[3 4]` | | `fold` | variadic | Reduce with function and initial value | `(fold + 0 [1 2 3])` → `6` | | `fold-left` | variadic | Left-associative fold | `(fold-left - 10 [1 2 3])` → `4` | diff --git a/docs/docs/language/math.md b/docs/docs/language/math.md index 859c9f90a..a849ec117 100644 --- a/docs/docs/language/math.md +++ b/docs/docs/language/math.md @@ -627,12 +627,24 @@ Keep elements of `vec` where the boolean `mask` is true. ### `(pmap f x)` -Parallel map. Like `map` but distributes work across threads for large inputs. +Currently a **sequential alias for `map`**: it calls the function for each +input element in order on the calling thread's VM and returns the same list as +`map`. A larger input or a larger `-c` setting does not distribute these callback +invocations across workers. The name can signal intent, but does not currently +provide parallel lambda evaluation. ```lisp -(pmap (fn [x] (* x x)) (til 1000000)) +(pmap (fn [x] (* x x)) [1 2 3]) ; (1 4 9), evaluated sequentially ``` +Eligible vector builtins and query operators can use the morsel-driven thread +pool **inside** a callback; that is separate from scheduling callbacks in +parallel. Eligibility, input-size thresholds and worker availability depend on +the operator; a large vector alone is not a guarantee of parallel execution. +See [Thread Pool](../architecture/pipeline.md#thread-pool). For independent +Rayfall task sweeps today, use separate processes and budget their workers as +described in [Multiple Instances](../guides/instances.md). + ## Quick Reference Table | Category | Functions | diff --git a/docs/docs/reference/all-functions.md b/docs/docs/reference/all-functions.md index d050624cd..e073ec148 100644 --- a/docs/docs/reference/all-functions.md +++ b/docs/docs/reference/all-functions.md @@ -218,7 +218,7 @@ Functions that take other functions as arguments for mapping, folding, and filte | Function | Type | Flags | Description | Example | |---|---|---|---|---| | `map` | variadic | — | Apply function to each element (returns a list) | `(map (fn [x] (* x 2)) [1 2 3])` → `(2 4 6)` | -| `pmap` | variadic | — | Parallel map (multi-threaded, returns a list) | `(pmap (fn [x] (* x x)) [1 2 3])` → `(1 4 9)` | +| `pmap` | variadic | — | Currently a sequential alias for `map` (returns a list) | `(pmap (fn [x] (* x x)) [1 2 3])` → `(1 4 9)` | | `fold` | variadic | — | Reduce with function and initial value | `(fold + 0 [1 2 3])` → `6` | | `fold-left` | variadic | — | Left-associative fold | `(fold-left - 10 [1 2 3])` → `4` | | `fold-while` | variadic | — | Fold that stops when the accumulator fails pred | `(fold-while (fn [a] (< a 100)) + 0 (til 1000))` → `105` | @@ -236,7 +236,7 @@ Functions that take other functions as arguments for mapping, folding, and filte ; Transform each row with map (map (fn [x] (* x x)) [1 2 3 4]) ; (1 4 9 16) -; Parallel map for expensive computation +; pmap currently uses the same sequential callback loop as map (pmap (fn [t] (sum (til t))) [100 200 300]) ; Running sum with scan