Skip to content

perf(bootstrap): draw all replicate samples in one vectorized call - #29

Merged
seanthimons merged 1 commit into
mainfrom
fix/vectorize-bootstrap-draws
Oct 20, 2026
Merged

seanthimons merged 1 commit into
mainfrom
fix/vectorize-bootstrap-draws

Conversation

@seanthimons

Copy link
Copy Markdown
Owner

Problem

generate_bootstrap_tasks() built one tibble::tibble() per replicate (about 0.7 ms each). For 10,000 replicates that is 7.8 s, and it runs on the calling R process before any worker starts. A Shiny app that starts a bootstrap freezes every session while this happens.

Change

Draw all replicates in a single stats::rbinom() call (a groups x replicates matrix) and build each sample with tibble::new_tibble().

rbinom() consumes the random stream in order, so the draws are identical to taking them one replicate at a time. Existing seeds keep producing exactly the same replicates; the output of the new function is identical() to the old one.

Measurements

Ward data, beta_poisson fit, this machine.

Task generation alone:

Replicates Before After
1,000 0.75 s 0.03 s
5,000 3.96 s 0.15 s
10,000 7.77 s 0.23 s

End to end, exponential + beta_poisson, 8 local workers (current dispatch):

Replicates per model App blocked at start (before / after) Total (before / after)
1,000 2.1 s / 0.1 s 6.1 s / 4.9 s
5,000 9.7 s / 0.4 s 18.3 s / 12.9 s
10,000 17.6 s / 1.0 s 32.7 s / 23.1 s

The "before" columns already include the separate dispatch fix in #28; this change is independent of it and touches a different function in the same file.

Tests

tests/testthat/test-bootstrap-tasks.R keeps the old algorithm as a reference and checks that the new output is identical to it:

  • observed and fitted probabilities, with a seed;
  • all three model types and a single replicate;
  • well-formed, numbered samples of the original groups;
  • seeded runs are reproducible and leave the global random stream alone;
  • bootstrap replicates are unchanged end to end on the sequential backend.

Full suite: 463 passed, 0 failed, 3 skipped.

Not in this PR

The mirai dispatch fix (#28), any app change, and version or NEWS edits.

generate_bootstrap_tasks() built one tibble per replicate (about 0.7 ms each),
which took 7.8 s for 10,000 replicates and blocked the calling R process; in a
Shiny app that freezes every session while a run starts.

Draw every replicate in a single rbinom() call and build each sample with
tibble::new_tibble(). rbinom() consumes the random stream in order, so the
output is identical to the old per-replicate draws for any seed, and existing
seeds keep producing the same replicates.

Add tests that compare against the old algorithm exactly, for observed and
fitted probabilities, all three models, and a single replicate.
@seanthimons
seanthimons marked this pull request as ready for review October 20, 2026 16:33
@seanthimons
seanthimons merged commit 380f686 into main Oct 20, 2026
10 checks passed
@seanthimons
seanthimons deleted the fix/vectorize-bootstrap-draws branch October 27, 2026 19:44
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.

1 participant