diff --git a/.jules/bolt.md b/.jules/bolt.md index b08b203a..66beb5eb 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-08-24 - Optimize computeTaskMetrics with Int32Array and standard loops +**Learning:** In hot loops over large array of tasks, using standard `for` loops and contiguous typed arrays (e.g., `Int32Array`) rather than standard `Map` caching and `Array.prototype.reduce`/`forEach` eliminates the overhead of JS engine callback allocation, garbage collection, and hash-lookups. +**Action:** Replace `Map` usages paired with functional iteration loops with standard indexed loops and TypedArrays for primitive data mappings whenever processing metrics for performance-critical high-iteration loops. diff --git a/app.js b/app.js index a04aae71..a9dc136a 100644 --- a/app.js +++ b/app.js @@ -1370,21 +1370,27 @@ function validateDateRange(startLabel, startValue, endLabel, endValue, errors) { } function computeTaskMetrics() { - // ⚡ Bolt: Cache durationDays during total calculation to avoid recalculating for every task - const durationCache = new Map(); - const totalDays = state.tasks.reduce((sum, task) => { + // ⚡ Bolt: Use Int32Array instead of Map for O(1) contiguous memory access without hash overhead + // ⚡ Bolt: Replace reduce/forEach with standard for loops to eliminate JS engine callback allocation + const len = state.tasks.length; + const durationCache = new Int32Array(len); + let totalDays = 0; + + for (let i = 0; i < len; i++) { + const task = state.tasks[i]; const duration = calculateDurationDays(task.plannedStartDate, task.plannedEndDate); - durationCache.set(task.id, duration); - return sum + duration; - }, 0); + durationCache[i] = duration; + totalDays += duration; + } const baseDate = state.baseDate; const byTask = new Map(); let totalWeightedPlannedRatio = 0; let totalWeightedActualRatio = 0; - state.tasks.forEach((task) => { - const durationDays = durationCache.get(task.id); + for (let i = 0; i < len; i++) { + const task = state.tasks[i]; + const durationDays = durationCache[i]; const weightRatio = totalDays > 0 ? durationDays / totalDays : 0; const plannedProgressRatio = calculatePlannedProgressRatio(baseDate, task.plannedStartDate, task.plannedEndDate, durationDays); const actualProgressRatio = (ACTUAL_PROGRESS_MAP[task.actualProgressStatus] || 0) / 100; @@ -1408,7 +1414,7 @@ function computeTaskMetrics() { plannedDateWarning, actualDateWarning }); - }); + } return { totalDays,