Skip to content

Avoid O(n^2) entry rescan in pumpEntries (slow for archives with many files) - #91

Open
ArneFeys wants to merge 1 commit into
thejoshwolfe:masterfrom
ArneFeys:perf-first-not-done-entry-index
Open

Avoid O(n^2) entry rescan in pumpEntries (slow for archives with many files)#91
ArneFeys wants to merge 1 commit into
thejoshwolfe:masterfrom
ArneFeys:perf-first-not-done-entry-index

Conversation

@ArneFeys

@ArneFeys ArneFeys commented Jun 3, 2026

Copy link
Copy Markdown

Problem

getFirstNotDoneEntry() (inside pumpEntries) rescans self.entries from index 0 on every pump. pumpEntries runs once per entry as each one completes, so building an archive is O(n²) in the number of entries. For archives with many files this becomes pathologically slow.

Fix

Entries are pumped strictly in array order — entry i+1 is only started once entry i reaches FILE_DATA_DONE — and an entry's state never regresses. So the index of the first not-done entry only ever advances. I cache it on the ZipFile (firstNotDoneEntryIndex) and resume the scan from there instead of restarting at 0, making the total work O(n).

The change is minimal and self-contained; output is byte-for-byte identical and the existing test/test.js suite passes unchanged.

Benchmark

Adding N tiny addBuffer(..., { compress: false }) entries, measuring total time to the output stream's end (same machine, Node 25):

entries before after speedup
50,000 1,608 ms 83 ms ~19×
100,000 6,570 ms 153 ms ~43×
200,000 33,504 ms 292 ms ~115×

Before scales ~4× per doubling of N (quadratic); after scales ~2× (linear). Output size was identical between the two for every N.

getFirstNotDoneEntry() restarted its scan at entries[0] on every pump, so
building an archive cost O(n^2) over the number of entries and got
pathologically slow for archives with many files.

Entries are pumped strictly in array order (entry i+1 only starts once
entry i reaches FILE_DATA_DONE) and an entry's state never regresses, so
the index of the first not-done entry only ever advances. Cache it on the
ZipFile and resume the scan from there, making the work O(n) overall.

Output is byte-for-byte identical; the existing test suite passes.
@ArneFeys
ArneFeys force-pushed the perf-first-not-done-entry-index branch from 6cf2637 to c97bafc Compare June 3, 2026 15:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant