Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/docs/architecture/pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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).

Expand Down
11 changes: 6 additions & 5 deletions docs/docs/guides/analytics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/language/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
Expand Down
16 changes: 14 additions & 2 deletions docs/docs/language/math.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/reference/all-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
Expand All @@ -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
Expand Down
Loading