Skip to content

Commit a890579

Browse files
committed
perf(output): Defer line-count and markdown-delimiter scans with lazy render-context getters
createRenderContext eagerly ran calculateFileLineCounts and calculateMarkdownDelimiter — two full scans over every packed file's content (~5.5MB) — on every run, even though fileLineCounts is only consumed by skill generation and markdownCodeBlockDelimiter only by the markdown/skill templates. Memoized getters defer both scans, so the default XML (and plain/JSON) path never pays for them. Output is byte-identical (verified with cmp for xml and markdown styles); when a template or packSkill touches the property, the same computation runs once and is cached. Why this clears the bar now when earlier rounds rejected it: this round's tail profiling showed that on warm runs (token cache hit) the metrics workers resolve instantly and git token tasks complete ~28ms before produceOutput, so generateOutput is the sole main-thread bottleneck at the tail — the scans are wall-visible, not hidden behind the parallel metrics branch as previously assumed. Benchmark (packing this repo, warm, quiet 4-core Linux, interleaved ABBA pairs, node bin/repomix.cjs --quiet): - batch 1 (20 pairs): median delta -21.7ms, mean -20.1ms, t=-3.51, 15/20 improved - batch 2 (30 pairs): median delta -25.6ms, mean -18.0ms, t=-2.49, 19/30 improved - batch 3 (40 pairs): median delta -40.3ms, mean -40.9ms, t=-6.71, 37/40 improved - pooled (90 pairs): mean -28.6ms on a ~1045ms baseline = -2.7% Isolated scan cost is ~11-16ms (warm); the larger e2e delta is consistent with reduced allocation/GC pressure from dropping the per-line match(/\n/g) array allocations at peak heap. intent(perf-tuning): automated round targeting >=2% end-to-end CLI improvement with behavior preserved decision(render-context): memoized getters over style-conditional skips — RenderContext shape and all call sites unchanged, and any consumer that does read the properties still gets identical values rejected(file-search): gitignore:false + prebuilt globby predicate measured -5.2% e2e but unshippable — depends on globby's unexported ignore.js internals (exports-map patch won't exist for npm consumers) and loses globby's gitignore-pattern directory pruning, risking large traversal regressions on repos with big ignored dirs and no negation patterns learned(metrics-tail): on warm runs the tail critical path is produceOutput -> write -> wrapper extraction; git diff/log token tasks finish ~28ms earlier, so render-side CPU cuts are wall-visible up to that slack https://claude.ai/code/session_01RD8vNvv1qtYV8BgdxMU7js
1 parent 0fe40b4 commit a890579

1 file changed

Lines changed: 13 additions & 2 deletions

File tree

src/core/output/outputGenerate.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ const calculateFileLineCounts = (processedFiles: ProcessedFile[]): Record<string
7676
};
7777

7878
export const createRenderContext = (outputGeneratorContext: OutputGeneratorContext): RenderContext => {
79+
// fileLineCounts is only consumed by skill generation and markdownCodeBlockDelimiter only by the
80+
// markdown/skill templates; both require a full scan over every file's content. Memoized getters
81+
// defer that scan so the default XML (and plain/JSON) path never pays for it.
82+
let fileLineCounts: Record<string, number> | undefined;
83+
let markdownCodeBlockDelimiter: string | undefined;
7984
return {
8085
generationHeader: generateHeader(outputGeneratorContext.config, outputGeneratorContext.generationDate),
8186
summaryPurpose: generateSummaryPurpose(outputGeneratorContext.config),
@@ -89,12 +94,18 @@ export const createRenderContext = (outputGeneratorContext: OutputGeneratorConte
8994
instruction: outputGeneratorContext.instruction,
9095
treeString: outputGeneratorContext.treeString,
9196
processedFiles: outputGeneratorContext.processedFiles,
92-
fileLineCounts: calculateFileLineCounts(outputGeneratorContext.processedFiles),
97+
get fileLineCounts() {
98+
fileLineCounts ??= calculateFileLineCounts(outputGeneratorContext.processedFiles);
99+
return fileLineCounts;
100+
},
93101
fileSummaryEnabled: outputGeneratorContext.config.output.fileSummary,
94102
directoryStructureEnabled: outputGeneratorContext.config.output.directoryStructure,
95103
filesEnabled: outputGeneratorContext.config.output.files,
96104
escapeFileContent: outputGeneratorContext.config.output.parsableStyle,
97-
markdownCodeBlockDelimiter: calculateMarkdownDelimiter(outputGeneratorContext.processedFiles),
105+
get markdownCodeBlockDelimiter() {
106+
markdownCodeBlockDelimiter ??= calculateMarkdownDelimiter(outputGeneratorContext.processedFiles);
107+
return markdownCodeBlockDelimiter;
108+
},
98109
gitDiffEnabled: outputGeneratorContext.config.output.git?.includeDiffs,
99110
gitDiffWorkTree: outputGeneratorContext.gitDiffResult?.workTreeDiffContent,
100111
gitDiffStaged: outputGeneratorContext.gitDiffResult?.stagedDiffContent,

0 commit comments

Comments
 (0)