diff --git a/.jules/bolt.md b/.jules/bolt.md index 8aef41f..63873a0 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -8,3 +8,7 @@ ## 2024-05-24 - Pre-compile RegExp in nested loops **Learning:** Instantiating `new RegExp()` inside nested array methods like `.filter` and `.some` creates a severe O(N*M) performance bottleneck, especially when matching two large lists (e.g., documented tests vs. actual test files). **Action:** Always pre-compile regular expressions and derived strings into an array of "matcher" objects outside of the loop before iterating, which shifts the instantiation cost from O(N*M) to O(N). + +## 2024-06-25 - [O(N*M) Basename Overhead] +**Learning:** In nested loops like array `.filter` and `.some`, repeatedly calling string manipulation functions like `basename(path)` inside the loop creates an O(N*M) bottleneck, executing the string parsing M times per item. +**Action:** Always precompute property lookups and string manipulations like `basename` into an array of mapped objects outside the loop before executing the cross-comparison, shifting the cost to strictly O(N). diff --git a/cli/commands/diff.mjs b/cli/commands/diff.mjs index 856eea1..7f73c5a 100644 --- a/cli/commands/diff.mjs +++ b/cli/commands/diff.mjs @@ -348,8 +348,8 @@ function diffTests(dir, config = {}) { // Glob-aware matching (documented entries are often patterns or basenames). const codeArr = [...codeTests]; - // PERFORMANCE OPTIMIZATION: Pre-compile regular expressions to avoid O(N*M) - // instantiation bottlenecks inside the nested .filter and .some loops below. + // PERFORMANCE OPTIMIZATION: Pre-compile regular expressions and replace O(N*M) + // nested `.filter` and `.some` loops with a single-pass Set-based cross-comparison. const docMatchers = [...docTests].map(docEntry => { const entry = String(docEntry).trim(); const hasSlash = entry.includes('/'); @@ -363,17 +363,26 @@ function diffTests(dir, config = {}) { }; }); - const matches = (matcher, codeRel) => { - const subject = matcher.hasSlash ? codeRel : basename(codeRel); - return matcher.rx.test(subject); - }; + const codeObjs = codeArr.map(c => ({ rel: c, base: basename(c) })); + const matchedDocs = new Set(); + const matchedCode = new Set(); + + for (const m of docMatchers) { + for (const c of codeObjs) { + const subject = m.hasSlash ? c.rel : c.base; + if (m.rx.test(subject)) { + matchedDocs.add(m); + matchedCode.add(c); + } + } + } return { title: 'Test Files', icon: '🧪', - onlyInDocs: docMatchers.filter(m => !codeArr.some(c => matches(m, c))).map(m => m.original), - onlyInCode: codeArr.filter(c => !docMatchers.some(m => matches(m, c))), - matched: docMatchers.filter(m => codeArr.some(c => matches(m, c))).map(m => m.original), + onlyInDocs: docMatchers.filter(m => !matchedDocs.has(m)).map(m => m.original), + onlyInCode: codeObjs.filter(c => !matchedCode.has(c)).map(c => c.rel), + matched: [...matchedDocs].map(m => m.original), }; } diff --git a/cli/validators/docs-diff.mjs b/cli/validators/docs-diff.mjs index f9fef2b..d360236 100644 --- a/cli/validators/docs-diff.mjs +++ b/cli/validators/docs-diff.mjs @@ -170,8 +170,8 @@ function diffTests(dir, config) { // Exact-string comparison produced the false "N documented but not found". const codeArr = [...codeTests]; - // PERFORMANCE OPTIMIZATION: Pre-compile regular expressions to avoid O(N*M) - // instantiation bottlenecks inside the nested .filter and .some loops below. + // PERFORMANCE OPTIMIZATION: Pre-compile regular expressions and replace O(N*M) + // nested `.filter` and `.some` loops with a single-pass Set-based cross-comparison. const docMatchers = [...docTests].map(docEntry => { const entry = String(docEntry).trim(); const hasSlash = entry.includes('/'); @@ -188,15 +188,24 @@ function diffTests(dir, config) { }; }); - const matches = (matcher, codeRel) => { - const subject = matcher.hasSlash ? codeRel : basename(codeRel); - return matcher.rx.test(subject); - }; + const codeObjs = codeArr.map(c => ({ rel: c, base: basename(c) })); + const matchedDocs = new Set(); + const matchedCode = new Set(); + + for (const m of docMatchers) { + for (const c of codeObjs) { + const subject = m.hasSlash ? c.rel : c.base; + if (m.rx.test(subject)) { + matchedDocs.add(m); + matchedCode.add(c); + } + } + } return { title: 'Test Files', - onlyInDocs: docMatchers.filter(m => !codeArr.some(c => matches(m, c))).map(m => m.original), - onlyInCode: codeArr.filter(c => !docMatchers.some(m => matches(m, c))), + onlyInDocs: docMatchers.filter(m => !matchedDocs.has(m)).map(m => m.original), + onlyInCode: codeObjs.filter(c => !matchedCode.has(c)).map(c => c.rel), }; }