perf: replace stats::stepfun with stepfun2 in expected_event#642
Conversation
…_event stats::stepfun has high creation overhead (~57x slower than stepfun2). In expected_event, step functions are created per call and evaluated on small vectors (3-5 elements), making creation cost dominant. Additionally, improve stepfun2 eval speed for small breakpoint vectors (n <= 10) by replacing findInterval with a for-loop of vectorized comparisons (x >= b). This avoids findInterval's dispatch overhead and binary search, which are overkill for 3-4 breakpoints. Net speedup for the step-function portion of expected_event: ~21x vs stats::stepfun, ~2x vs the previous findInterval-based stepfun2. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
yihui
left a comment
There was a problem hiding this comment.
@LittleBeannie I'm very confident that the new stepfun2() can safely replace stats::stepfun(), but please feel free to close the PR if you are not comfortable with changes in expected_event().
|
Thank you so much for your work on optimizing this. I really appreciate the effort and the improvements you've proposed. The Therefore, I will be closing this PR for now. This is purely a process decision to maintain stability and is not a reflection on the quality of your work, which is excellent. We can definitely re-open this or a similar PR in the future when the development roadmap for this module is updated. Thank you again for your contribution! |
Summary
stats::stepfun()calls withstepfun2()inexpected_event()stepfun2()itself: use a for-loop of vectorized comparisons (x >= b) for small breakpoint vectors (n <= 10), falling back tofindIntervalfor larger onesWhy
stats::stepfun()is 57x slower to create thanstepfun2()due to S3 class setup, environment construction, and method registration. Inexpected_event, step functions are created fresh on every call and evaluated once on small vectors (3-5 elements) — creation cost dominates.The loop approach avoids
findInterval's dispatch overhead and binary search, which are overkill for 3-4 breakpoints. Each iteration is a single vectorized primitive (x >= b), with no allocation.Benchmark (the 4-stepfun create+eval pattern in expected_event, 50k calls)
stats::stepfunstats::stepfunstepfun2(old,findInterval)stepfun2(new, loop for n<=10)Test plan
test-developer-expected_event.Rpassestest-independent-expected_event.Rpassesstepfun2produces identical results tostats::stepfunfor bothright = FALSEandright = TRUE🤖 Generated with Claude Code