diff --git a/.jules/bolt.md b/.jules/bolt.md index b08b203a..e8063851 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -4,3 +4,6 @@ ## 2026-07-12 - Optimize renderTaskRow DOM allocations **Learning:** Caching unattached template nodes and instantiating them via `.cloneNode(false)` reduces DOM instantiation overhead in O(N) render loops significantly. **Action:** Apply this optimization to other hot-path rendering elements such as rows, cells, and stack containers. +## 2026-07-12 - Eliminate padStart in hot-path Date formatters +**Learning:** Using `String.padStart()` in hot rendering loops (like timeline generation) introduces measurable execution time overhead compared to inline ternary operators (e.g., `value < 10 ? '0' + value : value`). +**Action:** Replace `padStart` with inline ternary string concatenation for zero-padding in frequently executed, performance-critical sections. diff --git a/app.js b/app.js index a04aae71..46b74b35 100644 --- a/app.js +++ b/app.js @@ -2684,15 +2684,19 @@ function clamp(value, min, max) { function formatDateInput(date) { const year = date.getUTCFullYear(); - const month = String(date.getUTCMonth() + 1).padStart(2, '0'); - const day = String(date.getUTCDate()).padStart(2, '0'); + const m = date.getUTCMonth() + 1; + const d = date.getUTCDate(); + const month = m < 10 ? '0' + m : m; + const day = d < 10 ? '0' + d : d; return `${year}-${month}-${day}`; } function formatLocalDateInput(date) { const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); + const m = date.getMonth() + 1; + const d = date.getDate(); + const month = m < 10 ? '0' + m : m; + const day = d < 10 ? '0' + d : d; return `${year}-${month}-${day}`; } diff --git a/tests/unit/date-formatter-benchmark.test.mjs b/tests/unit/date-formatter-benchmark.test.mjs new file mode 100644 index 00000000..abafc625 --- /dev/null +++ b/tests/unit/date-formatter-benchmark.test.mjs @@ -0,0 +1,50 @@ +import test from 'node:test'; +import assert from 'node:assert'; + +function padStartFormatter(date) { + const year = date.getUTCFullYear(); + const month = String(date.getUTCMonth() + 1).padStart(2, '0'); + const day = String(date.getUTCDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +} + +function ternaryFormatter(date) { + const year = date.getUTCFullYear(); + const m = date.getUTCMonth() + 1; + const d = date.getUTCDate(); + const month = m < 10 ? '0' + m : m; + const day = d < 10 ? '0' + d : d; + return `${year}-${month}-${day}`; +} + +test('Ternary operator is faster than padStart for date formatting in a realistic tight loop', () => { + const iterations = 500000; + // Use dates that represent a typical multi-year project range + const dates = Array.from({ length: 1000 }, (_, i) => new Date(Date.UTC(2023, 0, 1) + (i * 86400000))); + + // Warmup + for (let i = 0; i < 10000; i++) { + padStartFormatter(dates[i % 1000]); + ternaryFormatter(dates[i % 1000]); + } + + let len1 = 0; + const startPadStart = performance.now(); + for (let i = 0; i < iterations; i++) { + len1 += padStartFormatter(dates[i % 1000]).length; + } + const durationPadStart = performance.now() - startPadStart; + + let len2 = 0; + const startTernary = performance.now(); + for (let i = 0; i < iterations; i++) { + len2 += ternaryFormatter(dates[i % 1000]).length; + } + const durationTernary = performance.now() - startTernary; + assert.strictEqual(len1, len2, 'Output lengths should match'); + + console.log(`padStart execution time: ${durationPadStart.toFixed(2)}ms`); + console.log(`Ternary execution time: ${durationTernary.toFixed(2)}ms`); + + assert.ok(durationTernary < durationPadStart, 'Ternary formatter should be faster than padStart formatter'); +});