Skip to content

(perf): measure legend once - #724

Merged
grantmcdermott merged 4 commits into
mainfrom
perf-legend-measure
Sep 10, 2026
Merged

(perf): measure legend once#724
grantmcdermott merged 4 commits into
mainfrom
perf-legend-measure

Conversation

@grantmcdermott

Copy link
Copy Markdown
Owner

Some more Claude-assisted profiling golf. In short; avoiding remeasuring the legend unless the device resizes. Ekes out another ~5-8%.

Every legend was measured twice. draw_legend() measures once during
setup so par(oma) can be sized before the plot is drawn, and
tinylegend() measured again inside recordGraphics() because the device
may have been resized. On the initial draw nothing has changed between
the two, so the second pass re-derived a result it already had, at
roughly the cost of drawing the legend itself.

measure_fake_legend() now records the device it measured on, and
tinylegend() re-measures only when dev.size() no longer matches. Resize
correctness is preserved by construction rather than by trying to
detect a replay: a resized device fails the comparison and measures
again. Multi-legend is unaffected, since each draw_legend() call builds
its own legend_env and so carries its own stamp.

Measuring passes per plot drop from 2 to 1 (6 to 4 for multi-legend).
Plots that draw a legend are ~5% faster overall and ~8% for the
lighter ones (points_small_by 5.58ms -> 5.13ms); plots without a legend
are untouched. Full suite passes, 729/729, with NOT_CRAN=true.

Rendering was compared against main across 8 configurations replayed
through a sequence of device sizes (shrink, grow, back to the original,
then the same size again), covering outer and inner legends, facets,
gradient, multi-legend, ljust = "center" and a theme: byte-identical in
all 40 comparisons.

Claude-Session: https://claude.ai/code/session_01AjZRHA9ZvJkDPMFdDXUcUs
The device-size guard added in the previous commit calls dev.size()
without declaring it, which R CMD check flags as an undefined global.
Add it to the existing grDevices importFrom tag in legend.R and
regenerate NAMESPACE.

R CMD check is clean again (Status: OK).

Claude-Session: https://claude.ai/code/session_01AjZRHA9ZvJkDPMFdDXUcUs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The cache can reuse invalid measurements when replayed onto a different same-sized graphics device.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Optimizes legend rendering by caching measurements until the graphics device size changes.

Changes:

  • Cache legend dimensions across initial rendering.
  • Track the device size used for measurement.
  • Update imports and release notes.
File summaries
File Description
R/legend.R Adds legend measurement caching.
NAMESPACE Imports dev.size.
NEWS.md References the related performance PR.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread R/legend.R Outdated
Comment thread NEWS.md Outdated
dev.size() alone does not identify a device. Replaying a recorded plot
onto a different backend that happens to have the same dimensions (text
metrics differ by backend) would reuse the source device's measurement,
leaving outer margins and gradient swatches mis-sized. Include dev.cur(),
which is a named integer and so carries the backend as well as the
device number.

Demonstrated by recording on pdf and replaying onto svglite at a matching
8x8: main's replay matches a native draw on the destination, the previous
key's did not. With the device in the key it matches again.

Note this is a narrow hole rather than a common one: the everyday export
path, dev.copy2pdf(), re-measures under the old key anyway. It needs an
explicitly same-sized replay onto another backend. The guard costs one
dev.cur() call and does not move the benchmarks, so it is cheap insurance.

Also fixes the changelog typo flagged in review.

Spotted in review by Copilot on #724.

Claude-Session: https://claude.ai/code/session_01AjZRHA9ZvJkDPMFdDXUcUs
@grantmcdermott
grantmcdermott merged commit 15df8c2 into main Sep 10, 2026
3 checks passed
@grantmcdermott
grantmcdermott deleted the perf-legend-measure branch September 10, 2026 02:38
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