Skip to content

perf: cache Intl formatters - #1957

Merged
pedrolamas merged 3 commits into
fluidd-core:developfrom
KapJI:perf/cache-date-formatters
Sep 19, 2026
Merged

pedrolamas merged 3 commits into
fluidd-core:developfrom
KapJI:perf/cache-date-formatters

Conversation

@KapJI

@KapJI KapJI commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Found while investigating #1956, but independent of it.

formatDate, formatTime and formatDateTime pass a fresh locales array and options object to Date.toLocaleDateString and Date.toLocaleTimeString, so the browser builds a new Intl.DateTimeFormat on every call. Data tables format a date per cell on every render, which made those two functions 57% of a dashboard CPU profile on my printer.

Intl formatters are now reused through a small shared cache (src/util/intl-format-cache.ts), keyed by locales and options, so it holds one entry per distinct format:

  • Intl.DateTimeFormat: formatDate, formatTime, formatDateTime
  • Intl.RelativeTimeFormat: formatRelativeTime (e.g. per row in the Spoolman spool selection dialog and per notification)
  • Intl.NumberFormat: getReadableCurrencyString (the price column of the spool selection dialog)

Invalid dates still render as Invalid Date: Intl.DateTimeFormat.format throws on them, so they're handled explicitly. An unknown or missing currency still falls back to the plain value.

formatDateTime now always joins the date and time output. The previous combined path was only reachable when both formats were ISO, and its output was identical.

Per-call cost (Node microbenchmark):

Formatter Before After
Date/time ~50–60 µs ~2.5–5 µs
Currency ~30 µs ~1.3 µs

Measured on a dashboard with 507 file rows, with #1956 applied: the worst main-thread stall after a history load drops from 284 ms to 130 ms. The gain applies to any view that renders many dates.

Signed-off-by: Ruslan Sayfutdinov <ruslan@sayfutdinov.com>
@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Bundle size report (gzip)

Chunk Base Head Δ
assets/index-*.js 161.0 kB 161.1 kB +72 B
sw.js 11.2 kB 11.2 kB -5 B
assets/Timelapse-*.js 1.9 kB 1.9 kB +4 B
assets/AppBtnCollapseGroup-*.js 636 B 639 B +3 B
assets/ConsoleCard-*.js 2.0 kB 2.0 kB +3 B
assets/GcodePreview-*.js 369 B 366 B -3 B
assets/Icons-*.js 526 B 529 B +3 B
assets/AfcPrintStartDialogTool-*.js 2.3 kB 2.3 kB +2 B
assets/AppNamedSlider-*.js 1.7 kB 1.7 kB +2 B
assets/BeaconCard-*.js 4.3 kB 4.3 kB +2 B
assets/Diagnostics-*.js 15.9 kB 15.9 kB -2 B
assets/DiskUsageCard-*.js 3.2 kB 3.2 kB +2 B
assets/FullscreenCamera-*.js 421 B 423 B +2 B
assets/Jobs-*.js 497 B 499 B +2 B
assets/NotFound-*.js 435 B 437 B +2 B
assets/Settings-*.js 23.3 kB 23.3 kB -2 B
assets/Tune-*.js 3.4 kB 3.4 kB +2 B
assets/AppColorPicker-*.js 13.0 kB 13.0 kB +1 B
assets/Configure-*.js 776 B 775 B -1 B
assets/Console-*.js 366 B 365 B -1 B
assets/Dashboard-*.js 63.2 kB 63.2 kB -1 B
assets/JobHistoryItemStatus-*.js 1.2 kB 1.2 kB +1 B
assets/JobQueueCard-*.js 4.5 kB 4.5 kB -1 B
assets/MacroCategorySettings-*.js 2.0 kB 2.0 kB +1 B
assets/System-*.js 2.3 kB 2.3 kB +1 B
assets/TimelapseRenderSettingsDialog-*.js 1.8 kB 1.8 kB +1 B
Total 2.9 MB 2.9 MB +90 B

119 chunks compared, 26 changed. Sizes are gzip, matching what nginx serves.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The formatDateTime locale comparison is reference-based (making the single-formatter path effectively unreachable for array locales) and the cache key is order-sensitive, both undermining the intended performance gains.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Improves date/time formatting hot paths in Fluidd’s UI by reusing Intl.DateTimeFormat instances instead of rebuilding them per render/cell, reducing main-thread stalls in date-heavy tables.

Changes:

  • Add a keyed cache for Intl.DateTimeFormat instances and route formatDate/formatTime/formatDateTime through the cached formatter.
  • Preserve prior behavior for invalid dates by falling back to Date.toLocaleString(...) when Intl.DateTimeFormat.format(...) would throw.
  • Add unit tests covering caching behavior and invalid-date formatting.
File summaries
File Description
src/util/date-time-formatters.ts Introduces cached Intl.DateTimeFormat creation + shared formatting helper, used by date/time formatter APIs.
src/util/tests/date-time-formatters.spec.ts Adds tests validating formatter reuse, distinct formats, and invalid date rendering.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Lite

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

Comment thread src/util/date-time-formatters.ts Outdated
Comment thread src/util/date-time-formatters.ts Outdated
@pedrolamas

Copy link
Copy Markdown
Member

Hi @KapJI, thank you for submitting this PR.

I admit I’m a bit torn on this one; while I can see the impact, it doesn’t seem to be particularly significant according to your own metrics.

@KapJI

KapJI commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor Author

I think the improvement is significant.

Here is the fuller picture, measured on the same printer (507 gcode files, 328 printed, 265 jobs back-filled) with the CPU profiler over 30 s from a cold dashboard load, cache and service worker cleared:

Build Busy main-thread CPU In formatDate / formatTime Share of busy
v1.37.5 as released 29.8 s 18.3 s 61%
v1.37.5 + this PR 11.5 s 0.03 s 0.2%
#1956 only 2.65 s 0.41 s 16%
#1956 + this PR 2.15 s 0.002 s 0.1%

On the released build, rebuilding Intl.DateTimeFormat per cell is 61% of everything the page does after load, and this change alone takes busy CPU from 29.8 s to 11.5 s. On top of #1956 it is 410 ms to 2 ms, which is 16% of the remaining busy CPU here, and it scales with rows times date columns.

284 ms to 130 ms is what is left after #1956 removes the render storm, and it is the longest single main-thread stall (measured by timer drift), not CPU time.

@pedrolamas

Copy link
Copy Markdown
Member

That's fair, those numbers do paint another picture; my point was that once #1956 is merged, we go from 0.41s to 0.002s with these changes which is absolutely a reduction though not significant at that level.

Still, I do see value in this and will take it into consideration after #1956 gets merged.

Moves the Intl formatter cache into a shared intl-format-cache module
and reuses it for Intl.RelativeTimeFormat and the currency
Intl.NumberFormat. formatDateTime always joins date and time, and
secondsAsRange drops redundant checks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
@pedrolamas pedrolamas changed the title perf: cache date and time formatters perf: cache Intl formatters Sep 19, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approval recommended

Changes are localized, match the PR intent, and include targeted unit tests for the new caching behavior.

Review effort: Lite
Findings: None

Resolved since last review (2)

@pedrolamas
pedrolamas merged commit 470735f into fluidd-core:develop Sep 19, 2026
6 checks passed
@KapJI
KapJI deleted the perf/cache-date-formatters branch September 19, 2026 16:58
@KapJI

KapJI commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

By the way, this PR probably needs a label and a milestone assigned.

@pedrolamas pedrolamas added this to the 1.37.6 milestone Sep 21, 2026
@pedrolamas pedrolamas added the Code - Optimizations Everything that relates to code optimizations! label Sep 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Code - Optimizations Everything that relates to code optimizations!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants