Skip to content

pmap is documented as multi-threaded but runs sequentially #948

Open

Description

@vbmithr

Summary

The docs describe pmap as a multi-threaded parallel map, but it runs sequentially. ray_pmap_fn (src/ops/collection.c) returns ray_map_fn(args, n). The comment above it explains why: the VM is thread-local and evaluation is single-threaded. It says so since 949fd68, and since #125 it said "parallel not implemented yet".

The user-facing docs still promise parallelism:

  • docs/docs/language/functions.md and docs/docs/reference/all-functions.md: "Parallel map (multi-threaded, returns a list)".
  • docs/docs/language/math.md: "Like map but distributes work across threads for large inputs".
  • docs/docs/guides/analytics.md: "Use pmap for embarrassingly parallel work. It distributes a function across list elements using worker threads."

Reproduction

On dev 62d9bffa, release build, run as rayforce -c 8 pm.rfl:

(set work (fn [k] (do (set acc 0) (set i 0)
  (while (< i 300000) (set acc (+ acc (* i k))) (set i (+ i 1))) acc)))
(println (timeit (map work (til 8))))   ; 869.8 ms
(println (timeit (pmap work (til 8))))  ; 845.3 ms

The two take the same time, and pmap uses one core.

Expected

  1. At least: the docs say what pmap does: a sequential alias for map that marks intent. They should also say where Rayforce does run in parallel: inside builtins and the query engine, over morsels, for vectors of at least RAY_PARALLEL_THRESHOLD elements. Otherwise users pick pmap for work it cannot speed up.
  2. As a feature, if you want it: real parallel evaluation of independent lambda calls. Several restricted designs would already help, for example worker VMs that see globals read-only and return only values.

Use case

A backtest parameter sweep: many independent runs of the same Rayfall engine, one per configuration. Each run is an interpreted time loop over small per-instrument vectors (100–500 elements), so morsel parallelism never applies. The whole sweep therefore runs on one core.

Today the workaround is several processes. A coordinator .ipc.posts a job to each of several rayforce -p workers, then collects the results with .ipc.send. With 4 workers, the same work went from 1,818 ms to 438 ms (4.1×) with identical results. That works, but each worker loads the data again and holds its own copy in memory.

Activity

  1. morsel-ray commented on Oct 22, 2026

    @morsel-ray
    Collaborator

    Confirmed the source contract at 62d9bff: ray_pmap_fn directly calls ray_map_fn, whose callback loop is sequential. I took the documentation correction only, covering the reference, math guide and analytics recommendation; real parallel lambda evaluation remains a separate design, not an implied existing capability.

    I'll also distinguish callback scheduling from eligible builtin/query kernels inside a callback: those may use the pool independently. A large input or -c 8 doesn't by itself parallelize the map/pmap calls, and there isn't one universal threshold for every operator. I haven't independently rerun your timings or process-memory comparison.

    Documentation landed via #949 → d4899d91 on dev, after independent review of 87b49bc5 and eleven successful checks; complete checked/landed tree matched. Strict MkDocs/anchors and the attributed pmap3/3/value examples passed. This changes five Markdown files, not callback scheduling: real parallel lambda evaluation remains separate, and I have not independently repeated the timing/memory claims.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions