Skip to content

Cut 9 ms of cold start from static export at 10M points - #284

Merged
Alek99 merged 1 commit into
mainfrom
alek/snapshot-10m-cold-cost
Jul 25, 2026
Merged

Cut 9 ms of cold start from static export at 10M points#284
Alek99 merged 1 commit into
mainfrom
alek/snapshot-10m-cold-cost

Conversation

@Alek99

@Alek99 Alek99 commented Jul 25, 2026

Copy link
Copy Markdown
Member

Why

The docs headline Snapshot at 10 million points publishes 0.0232 s for the static CPU PNG. Profiling that exact measured region shows ~71% of it is first-call overhead, not data work:

ms (this machine)
cold figure() 12.7–13.7
cold to_png() 14.2–14.8
cold total — what the docs report 27.4–27.8
same two calls repeated in-process 7.7–7.9

The real 10M kernel work is under 8 ms and already memory-bound at 88–105 GB/s. Two non-engine costs dominate the rest, and this PR removes both.

Neither is visible to CodSpeed, by construction: its largest scatter is LARGE_N = 1_000_000, and the session-autouse warm_lazy_modules fixture deliberately warms lazy imports before every measured region (it was added for good reason — it killed a phantom 26% regression — but it means the dominant term of the published number cannot be observed there).

What changed

1. Vendor xml.sax.saxutils.escape into _svg.py. Importing it pulls urllib.requesthttp.clientssl, socket and the whole email package — 35+ modules for a function whose body is three str.replace calls. Nothing else in xy needs that tree. All 46 call sites are untouched.

2. Raster export skips the density point-sample overlay. density["sample"] has exactly one writer and zero Python readers_raster._emit_grid draws density traces from the grid alone. At 10M that meant running a SplitMix predicate over all 10M rows and gathering two 8,222-row columns that no pixel consumes. _build_raster_payload now passes point_overlay=False; the public build_payload/build_payload_split still ship the overlay because the browser client (js/src/50_chartview.ts) draws it.

Measured

Interleaved cold-process A/B at N=10M, 6 rounds, alternating order:

main this branch
cold to_png 14.8 ms 5.7 ms
cold total 28.4 ms 19.2 ms (−32%)

Proof of no functionality loss

  • 72 artifacts hash-identical to main — PNG at scale 1 and 2, SVG, HTML, wire blob and wire spec, across 12 figure configurations: density, direct tier, float32 input, clipped domain, continuous colour, NaN-injected, log axes, categorical at 2.2M, line, and one carrying & < > " ' plus non-BMP characters in its title so escape is genuinely exercised.
  • Full suite: 2283 passed, 4 skipped.
  • ruff check and ruff format --check clean; ty reports the same 13 pre-existing diagnostics as main.
  • 19 new tests in two files:
    • tests/test_svg_escape.py — differential fuzz of the vendored escape against the stdlib (exhaustive over all strings of length ≤3 on a hostile alphabet, plus 20k random strings × 5 entity dicts), and a fresh-subprocess assertion that a static SVG export leaves urllib.request/ssl/http.client/email/socket unimported. The subprocess matters: import pytest alone loads email, which would mask the regression in-process.
    • tests/test_raster_density_overlay.py — pins both halves of the contract (wire payload keeps sample, raster payload drops it), asserts the raster grid bytes equal the wire grid bytes, and covers the compact-categorical and clipped-domain branches plus the direct tier.
  • Spec updated per CLAUDE.md: spec/api/export.md, and the dossier's DENSITY_SAMPLE_TARGET row and §33 import budget.

Reviewer notes

  • The saxutils win is cold-start-specific. If the host process already imported urllib — a web server, requests, or benchmarks/_launch_interactive.py itself — it collapses to ~1.2 ms. It is a real win for cold CLI/script/serverless exports, which is exactly what the static benchmark measures, but a long-lived app will not see the full number.
  • The overlay skip is fully real and scales with N: it applies to every to_png/to_jpeg/to_webp/savefig of any scatter past the density threshold.
  • to_svg pays the same dead-overlay cost (~1.4 ms) because it goes through the public build_payload. Left as a follow-up — it needs its own private payload path, which is a wider change than this one.
  • The published 0.0232 s and 0.283 GiB rows are backed by a committed baseline at benchmarks/launch_baselines/, recorded on an M5 Pro. They need regenerating on that reference machine, not on the machine these numbers came from.
  • The docs-app-codespell pre-commit hook could not run locally (it cannot install codespell in this sandbox); it fails identically on an unchanged main tree, so it is environmental. Changed files were spell-checked directly.

Summary by CodeRabbit

  • Performance

    • Improved raster density exports by omitting unused point overlays while preserving identical grid and image output.
    • Reduced unnecessary processing for large scatter datasets.
  • Compatibility

    • SVG escaping remains consistent with standard behavior while reducing export overhead.
  • Documentation

    • Clarified density overlay behavior for raster, browser, and SVG outputs.
  • Tests

    • Added coverage for raster output consistency, categorical and clipped density plots, and SVG escaping behavior.

The docs "Snapshot at 10 million points" (0.0232 s) is ~71% first-call
overhead, not data work: a cold static PNG at 10M measures 27.4 ms here,
while repeating the same two calls in-process costs 7.8 ms. Two non-engine
costs dominate, and neither is visible to CodSpeed — its largest scatter is
1M, and warm_lazy_modules deliberately warms lazy imports before every
measured region.

Vendor xml.sax.saxutils.escape into _svg.py. Importing it pulls
urllib.request -> http.client -> ssl, socket and all of email: 35+ modules
for a function whose body is three str.replace calls. Nothing else in xy
needs that tree.

Skip the density point-sample overlay on raster export. density["sample"]
has one writer and zero Python readers -- _raster._emit_grid draws density
traces from the grid alone -- so at 10M it ran a SplitMix predicate over all
10M rows and gathered two 8,222-row columns that no pixel consumed. The
public build_payload keeps shipping it; the browser client draws it.

Measured, interleaved cold A/B at 10M over 6 rounds: to_png 14.8 -> 5.7 ms,
cold total 28.4 -> 19.2 ms (-32%). Output is unchanged: 72 artifacts
(PNG at scale 1 and 2, SVG, HTML, wire blob, wire spec) across 12 figure
configurations -- density, direct, f32, clipped, continuous colour,
NaN-injected, log axes, categorical at 2.2M, line -- all hash-identical to
main. Full suite 2283 passed; ty reports the same 13 pre-existing diagnostics.
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Raster exports now omit sampled density point overlays while wire payloads retain them. SVG export uses a local XML escaping implementation, with documentation and regression tests covering payload bytes, rendering determinism, escaping compatibility, and static-export imports.

Changes

Raster density overlay handling

Layer / File(s) Summary
Raster payload overlay control
python/xy/_payload.py
Adds the point_overlay writer option, disables it for raster payloads, and skips density sample generation while preserving grid and visible-count behavior.
Raster overlay regression coverage
tests/test_raster_density_overlay.py, spec/api/export.md, spec/design-dossier.md
Tests wire/raster payload differences, grid bytes, PNG determinism, density branches, and clipped counts; documentation describes the raster and wire contracts.

Static SVG escaping

Layer / File(s) Summary
Vendored SVG escaping
python/xy/_svg.py, spec/design-dossier.md
Replaces the stdlib escape import with a local implementation and documents the static-export import constraint.
SVG escape compatibility checks
tests/test_svg_escape.py
Compares local escaping with the stdlib across exhaustive and randomized inputs and verifies static SVG export imports.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: farhanaliraza

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main performance change to static export cold start at 10M points.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch alek/snapshot-10m-cold-cost

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/test_svg_escape.py (1)

64-82: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Make the cold-import assertion precise.

NumPy is imported before xy, so the test can falsely attribute NumPy’s imports to xy. It also omits xml.sax.saxutils, allowing the removed import to regress without failing this guard. Snapshot sys.modules after NumPy import, compare the post-export delta, and include xml.sax.saxutils in the checked set.

Suggested test adjustment
-        "import sys; import numpy as np; import xy\n"
+        "import sys; import numpy as np\n"
+        "before_xy = set(sys.modules)\n"
+        "import xy\n"
...
-        "leaked = {'urllib.request', 'ssl', 'http.client', 'email', 'socket'} & set(sys.modules)\n"
+        "leaked = {'xml.sax.saxutils', 'urllib.request', 'ssl', 'http.client', 'email', 'socket'} & (set(sys.modules) - before_xy)\n"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_svg_escape.py` around lines 64 - 82, Update
test_svg_export_does_not_import_urllib_or_ssl so the subprocess snapshots
sys.modules immediately after importing NumPy, then computes only the modules
newly loaded by importing xy and exporting SVG. Add xml.sax.saxutils to the
forbidden module set alongside the existing network-stack modules, and assert
against the post-snapshot delta.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/xy/_payload.py`:
- Around line 1131-1134: Record a stable omission reason in the density
specification when the point overlay raster path has no sample, updating the
branch around _density_sample_spec and preserving any existing rows_exceed_u32
reason. Add or update the raster density overlay test to assert the new reason
field while keeping normal sample emission unchanged.

---

Nitpick comments:
In `@tests/test_svg_escape.py`:
- Around line 64-82: Update test_svg_export_does_not_import_urllib_or_ssl so the
subprocess snapshots sys.modules immediately after importing NumPy, then
computes only the modules newly loaded by importing xy and exporting SVG. Add
xml.sax.saxutils to the forbidden module set alongside the existing
network-stack modules, and assert against the post-snapshot delta.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 691add10-2578-4136-bc19-02488182fdfd

📥 Commits

Reviewing files that changed from the base of the PR and between 077c53f and 381a6e3.

📒 Files selected for processing (6)
  • python/xy/_payload.py
  • python/xy/_svg.py
  • spec/api/export.md
  • spec/design-dossier.md
  • tests/test_raster_density_overlay.py
  • tests/test_svg_escape.py

Comment thread python/xy/_payload.py
Comment on lines +1131 to +1134
if pw.point_overlay:
sample = self._density_sample_spec(t, sel, visible, xr, yr, pw, sample_sel=sample_sel)
if sample is not None:
density["sample"] = sample

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Record raster overlay omission in the emitted density spec.

This branch drops density["sample"] without adding an omission reason. Unlike the existing rows_exceed_u32 path, consumers and diagnostics cannot distinguish the intentional raster-export omission from an absent or broken sample. Add a stable spec field and assert it in tests/test_raster_density_overlay.py while preserving any existing oversized-row reason.

As per coding guidelines, every decimation or tier decision must be recorded in the specification rather than made silently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/xy/_payload.py` around lines 1131 - 1134, Record a stable omission
reason in the density specification when the point overlay raster path has no
sample, updating the branch around _density_sample_spec and preserving any
existing rows_exceed_u32 reason. Add or update the raster density overlay test
to assert the new reason field while keeping normal sample emission unchanged.

Source: Coding guidelines

@codspeed-hq

codspeed-hq Bot commented Jul 25, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 102 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing alek/snapshot-10m-cold-cost (381a6e3) with main (077c53f)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@Alek99
Alek99 merged commit a7239b9 into main Jul 25, 2026
28 checks passed
Alek99 added a commit that referenced this pull request Jul 25, 2026
Review on #284: dropping the point-sample overlay on the raster path left no
trace in the spec, which contradicts the §28 rule that no decimation decision
is silent. The oversized branch already records
overlay_omitted = "rows_exceed_u32"; this adds "static_raster" for the new
case, guarded so it cannot mask the more fundamental u32 reason when both
apply. Tests pin the field on the raster payload and its absence on the wire
payload, so the raster-only flag can never leak to the client.

Also tighten the import guard: it now measures modules imported after NumPy,
so NumPy's own tree is never attributed to xy, and it checks for
xml.sax.saxutils directly instead of only the urllib/ssl/email fallout that
import happens to drag in today. A future slimmer saxutils would otherwise let
the removed import creep back without failing the test named for it.

overlay_omitted is written only into the private raster spec, which no
renderer reads: all 72 artifact hashes across 12 figure configurations are
unchanged. Full suite 2283 passed.
masenf pushed a commit that referenced this pull request Jul 27, 2026
Review on #284: dropping the point-sample overlay on the raster path left no
trace in the spec, which contradicts the §28 rule that no decimation decision
is silent. The oversized branch already records
overlay_omitted = "rows_exceed_u32"; this adds "static_raster" for the new
case, guarded so it cannot mask the more fundamental u32 reason when both
apply. Tests pin the field on the raster payload and its absence on the wire
payload, so the raster-only flag can never leak to the client.

Also tighten the import guard: it now measures modules imported after NumPy,
so NumPy's own tree is never attributed to xy, and it checks for
xml.sax.saxutils directly instead of only the urllib/ssl/email fallout that
import happens to drag in today. A future slimmer saxutils would otherwise let
the removed import creep back without failing the test named for it.

overlay_omitted is written only into the private raster spec, which no
renderer reads: all 72 artifact hashes across 12 figure configurations are
unchanged. Full suite 2283 passed.
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