Skip to content

perf: memoise Intl formatters in date bucketing (#99) - #109

Merged
Carl723000 merged 2 commits into
ClaudeCodeUsage:mainfrom
rsyuzyov:perf/cache-intl-formatters
Sep 22, 2026
Merged

Carl723000 merged 2 commits into
ClaudeCodeUsage:mainfrom
rsyuzyov:perf/cache-intl-formatters

Conversation

@rsyuzyov

Copy link
Copy Markdown
Contributor

Fixes the CPU path reported in #99.

Problem

dayKeyInZone, monthKeyInZone and hourKeyInZone are called once per ingested record, and each call constructed a fresh Intl.DateTimeFormat — twice, in fact, because resolveTimeZone builds one of its own to canonicalise the zone. Formatter construction, not parsing, dominates indexing on a large local history.

CPU profile taken against a live, pegged extension host (VS Code 1.104, Windows 11, extension 2.3.3, ~1.4 GB of local history in ~/.claude/projects), 6 s at 200 µs sampling:

30.4%  resolveTimeZone   out/dateKeys.js:18
26.5%  partsInZone       out/dateKeys.js:28
18.3%  (anonymous)       out/claudeIncrementalIndex.js:902
 3.0%  analyzeLine       out/dataLoader.js:243
 2.4%  estimateTokens    out/dataLoader.js:165

57% of samples are the two functions in dateKeys.ts. The host held one core at 100% and 2 GB RSS continuously; because live sessions keep appending, the incremental index never catches up.

Fix

Memoise the resolved zone and both formatters per zone. Keys are byte-identical, including the empty-zone and invalid-zone fallbacks, and each map holds one entry per configured zone — a handful for the life of the process.

Measurement

200k records, day + hour key for each, Asia/Yekaterinburg, Node 24.8, old implementation inlined next to the new one in the same process:

records/s time
before 1,059 188.8 s
after 167,982 1.2 s

158x, with 0 key mismatches across the comparison set.

Tests

Two cases added to src/test/dateKeys.test.ts: one proves the formatter is built once per zone and not per record (counting constructor via Proxy, in a zone the other cases do not touch, so the cache starts cold), one guards that memoised zones keep their calendars apart.

dateKeys suite: 14/14 pass. Full npm test on this machine: 1026/1036 pass. The 9 failures are all Windows filesystem-permission cases (EPERM: symlink, temp-dir rename/rmdir races) in the Codex lease and manifest suites; the same files fail identically on a clean main here (155/163 both before and after), so they are environmental and unrelated to this change.

Day, month and hour keys are derived once per ingested record, and each call
built a new Intl.DateTimeFormat - twice, because resolveTimeZone constructed
one of its own. Formatter construction dominates that path: a CPU profile of a
pegged extension host attributed 57% of samples to resolveTimeZone and
partsInZone alone.

Memoise the resolved zone and both formatters per zone. Keys are unchanged,
including the empty-zone and invalid-zone fallbacks; the cache holds one entry
per configured zone. Measured on a 1.4 GB local history: 1,059 -> 167,982
records/second (158x), 0 key mismatches over the comparison set.
@github-actions

Copy link
Copy Markdown

🤖 Automated first-pass review

The configured analysis model did not return a usable analysis for this pull request. The item has still been received and remains available for maintainer review.

To help with review, please ensure the change summary, tests, and any user-facing documentation or localization updates are included.


🤖 Posted automatically by CCU Bot — not a maintainer decision.

@Carl723000
Carl723000 merged commit c8ba351 into ClaudeCodeUsage:main Sep 22, 2026
4 checks passed
Carl723000 pushed a commit that referenced this pull request Sep 23, 2026
formatUsageDate called toLocaleDateString once per table row and chart
point, constructing a fresh Intl.DateTimeFormat each time. That is the
same per-call cost #109 removed from dateKeys, and it showed up at 3-8%
self time in extension-host profiles captured while the dashboard was
rendering.

Formatters are now cached per locale and options. When no date field is
requested, the numeric year/month/day default that toLocaleDateString
applies is added explicitly, so output stays identical. A new test
compares the result against toLocaleDateString for all eight UI locales
across several option shapes, including the cached second call.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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