Skip to content

feat: opt-in multithreaded fill for synthetic data generators - #35

Merged
ptomecek merged 1 commit into
mainfrom
feat/threaded-synthetic-gen
Sep 1, 2026
Merged

feat: opt-in multithreaded fill for synthetic data generators#35
ptomecek merged 1 commit into
mainfrom
feat/threaded-synthetic-gen

Conversation

@ptomecek

@ptomecek ptomecek commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

The synthetic data generators (scan_synthetic_regression, scan_synthetic_panel) are dominated by single-threaded numpy standard_normal draws (~0.9 GB/s), leaving cores idle — 10–70× slower than a parquet read on the same data. This adds an opt-in n_workers parameter that fills the x/eps Gaussian draws in parallel.

Approach

  • Draws are generated in fixed-size blocks (n_workers * fetch_size rows), each split across worker threads that fill disjoint slices in place via numpy's out=. standard_normal releases the GIL, so the fill scales across cores.
  • n_workers=1 (default) is byte-for-byte identical to the previous serial path — the change is purely additive.
  • Reproducibility is re-keyed to (seed, n_workers, fetch_size) and stays independent of the batch_size Polars picks, so is_pure remains sound.
  • Per-worker seed sub-streams are spawned lazily per block, keeping seed storage at O(n_workers) and preserving bounded, streaming memory.

Measured throughput (32 cores)

Case serial threaded speedup
regression 5M × (10+1) ~10 M rows/s ~27–37 M rows/s ~3–3.7×
panel, large universe ~1.6×

Speedup for the regression path is Amdahl-bound by the serial DataFrame build; panels benefit proportionally to n_symbols (the per-date batch size).

Tests

  • n_workers=1 byte-parity with the serial path; determinism per (seed, n_workers).
  • Runtime batch_size independence (in-memory vs streaming engine), incl. a full panel case with categories + group_by + use_weights.
  • Direct cross-block take() chunking-invariance (the highest-risk logic).
  • OLS/WLS β-recovery, head/projection pushdowns, early-termination, invalid args.

Full io_sources suite green; ruff check + ruff format --check clean.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Test Results

1 463 tests  +9   1 415 ✅ +9   42s ⏱️ +2s
    2 suites ±0      48 💤 ±0 
    2 files   ±0       0 ❌ ±0 

Results for commit 76dc72e. ± Comparison against base commit 363a4f8.

♻️ This comment has been updated with latest results.

The synthetic generators are dominated by single-threaded numpy
standard_normal draws (~0.9 GB/s), leaving cores idle. Add an opt-in
n_workers parameter to scan_synthetic_regression and scan_synthetic_panel
that fills the x/eps Gaussian draws in parallel.

Draws are generated in fixed-size blocks (n_workers * fetch_size rows),
each split across worker threads that fill disjoint slices in place via
out=. standard_normal releases the GIL, so the fill scales across cores
(~3x for regression, universe-dependent for panels). Reproducibility is
re-keyed to (seed, n_workers, fetch_size) and stays independent of the
batch_size Polars picks, so is_pure remains sound. n_workers=1 (default)
keeps the exact serial path and its byte-for-byte output.

Per-worker seed sub-streams are spawned lazily per block to keep seed
storage at O(n_workers) and preserve bounded, streaming memory.

Signed-off-by: Pascal Tomecek <40371786+ptomecek@users.noreply.github.com>
@ptomecek
ptomecek force-pushed the feat/threaded-synthetic-gen branch from a171e17 to 76dc72e Compare September 1, 2026 15:32
@ptomecek
ptomecek marked this pull request as ready for review September 1, 2026 15:51
@ptomecek
ptomecek merged commit 36d2260 into main Sep 1, 2026
6 checks passed
@ptomecek
ptomecek deleted the feat/threaded-synthetic-gen branch September 1, 2026 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants