@zip.js/zip.js 2.18.2 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BENCHMARKS.md +172 -144
- package/deno.json +1 -1
- package/dist/zip-core-external.js +192 -120
- package/dist/zip-core-external.min.js +1 -1
- package/dist/zip-core.js +193 -119
- package/dist/zip-core.min.js +1 -1
- package/dist/zip-fs-core-external.js +192 -120
- package/dist/zip-fs-core-external.min.js +1 -1
- package/dist/zip-fs-core.js +193 -119
- package/dist/zip-fs-core.min.js +1 -1
- package/dist/zip-fs-external.js +192 -120
- package/dist/zip-fs-external.min.js +1 -1
- package/dist/zip-fs-native.js +195 -121
- package/dist/zip-fs-native.min.js +1 -1
- package/dist/zip-fs.js +194 -120
- package/dist/zip-fs.min.js +1 -1
- package/dist/zip-legacy.js +195 -121
- package/dist/zip-legacy.min.js +1 -1
- package/dist/zip-native.js +195 -121
- package/dist/zip-native.min.js +1 -1
- package/dist/zip-web-worker-native.js +1 -1
- package/dist/zip-web-worker.js +1 -1
- package/dist/zip.js +194 -120
- package/dist/zip.min.js +1 -1
- package/index-native.cjs +195 -121
- package/index-native.min.js +1 -1
- package/index.cjs +194 -120
- package/index.d.cts +95 -26
- package/index.d.ts +95 -26
- package/index.min.js +1 -1
- package/lib/core/codec-pool.js +1 -2
- package/lib/core/codec-worker-web.js +11 -39
- package/lib/core/codec-worker.js +1 -2
- package/lib/core/configuration.js +1 -1
- package/lib/core/options.js +0 -2
- package/lib/core/streams/codec-stream.js +1 -1
- package/lib/core/version.js +1 -1
- package/lib/core/web-worker-base.js +2 -3
- package/lib/core/web-worker-inline-native.js +1 -1
- package/lib/core/web-worker-inline-wasm.js +1 -1
- package/lib/core/zip-reader.js +100 -40
- package/lib/core/zip-writer.js +75 -34
- package/lib/zip-core-reader.js +2 -0
- package/package.json +1 -1
- package/worker-message-property-names.js +1 -1
package/BENCHMARKS.md
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# Benchmarks
|
|
2
2
|
|
|
3
3
|
Measurements of [@zip.js/zip.js](https://www.npmjs.com/package/@zip.js/zip.js) against
|
|
4
|
-
[jszip](https://github.com/Stuk/jszip), [fflate](https://github.com/101arrowz/fflate)
|
|
5
|
-
[archiver](https://github.com/archiverjs/node-archiver)
|
|
4
|
+
[jszip](https://github.com/Stuk/jszip), [fflate](https://github.com/101arrowz/fflate),
|
|
5
|
+
[archiver](https://github.com/archiverjs/node-archiver) and the
|
|
6
|
+
[ZIP API of `node:zlib`](https://nodejs.org/api/zlib.html#class-zlibzipentry), of zip.js's own
|
|
7
|
+
codec backends, and of
|
|
6
8
|
the runtimes it runs on. Every number on this page comes from the harness in
|
|
7
9
|
[`benchmarks/`](benchmarks/), on the machine, date and versions below. The results are specific
|
|
8
10
|
to them; run the harness on your machine before relying on any of them.
|
|
9
11
|
|
|
10
12
|
The page answers four questions, each from one or two scripts of the harness:
|
|
11
13
|
|
|
12
|
-
- [How does zip.js compare with jszip, fflate and
|
|
14
|
+
- [How does zip.js compare with jszip, fflate, archiver and node:zlib?](#zipjs-against-jszip-fflate-archiver-and-nodezlib)
|
|
13
15
|
`bench.js`, on Node.
|
|
14
16
|
- [Which codec backend of zip.js is fastest, and what does concurrent `add()` buy?](#the-codec-backends-of-zipjs)
|
|
15
17
|
`bench-codecs.js` and `bench-backends.js`, on Node.
|
|
@@ -22,14 +24,15 @@ The page answers four questions, each from one or two scripts of the harness:
|
|
|
22
24
|
| | |
|
|
23
25
|
|---|---|
|
|
24
26
|
| Machine | Apple M2, 8 cores (4 performance + 4 efficiency), 16 GB RAM |
|
|
25
|
-
| OS | macOS
|
|
26
|
-
| Runtimes | Node.js v26.
|
|
27
|
-
| Browsers | Firefox
|
|
28
|
-
| zip.js | 2.
|
|
27
|
+
| OS | macOS 27.0 (arm64) |
|
|
28
|
+
| Runtimes | Node.js v26.10.0, Bun 1.4.2, Deno 2.9.7 |
|
|
29
|
+
| Browsers | Firefox 156, Chrome 153, headless (the browser encryption table only) |
|
|
30
|
+
| zip.js | 2.19.0 |
|
|
29
31
|
| jszip | 3.10.2 |
|
|
30
32
|
| fflate | 0.8.3 |
|
|
31
33
|
| archiver | 8.0.0 |
|
|
32
|
-
|
|
|
34
|
+
| node:zlib | the ZIP API built into Node.js 26.10.0, experimental, added in 26.8.0 |
|
|
35
|
+
| Measured | 2026-09-30; the browser encryption table on 2026-09-26 |
|
|
33
36
|
|
|
34
37
|
## Method
|
|
35
38
|
|
|
@@ -51,29 +54,37 @@ The page answers four questions, each from one or two scripts of the harness:
|
|
|
51
54
|
table; it is not a verdict.
|
|
52
55
|
- **Memory.** Peak resident set size reported by `/usr/bin/time -l`, as a delta over an empty
|
|
53
56
|
Node process (about 50 MB). It is the highest of the 3 runs, while the time is their median.
|
|
54
|
-
- **
|
|
57
|
+
- **Versions of this page.** A re-run replaces the whole page, and the runtimes, browsers and OS
|
|
58
|
+
move between two runs along with zip.js, so a number read from an older version of the page
|
|
59
|
+
is not comparable with one here: only a column measured in the same run isolates a single
|
|
60
|
+
variable, as the `chunkSize` 64 KB column of the
|
|
61
|
+
[runtime table](#concurrent-add-per-runtime) does for the default chunk size.
|
|
62
|
+
- **Level.** Every library compresses at its own level 6; the ZIP API of `node:zlib` has no level
|
|
63
|
+
option and deflates at zlib's default, which is 6. A level is not a unit shared between
|
|
55
64
|
libraries: zlib's level 6 and fflate's level 6 are different parameter sets and produce
|
|
56
65
|
different sizes, so every time is printed next to the size it achieved, and the
|
|
57
66
|
[Codecs](#codecs-at-equal-output-size) table compares by output size instead.
|
|
58
|
-
- **Codecs.** Every head-to-head table names the codec of each row. archiver
|
|
59
|
-
module, the host's zlib written in C; jszip (pako) and fflate
|
|
60
|
-
JavaScript. zip.js selects its codec at runtime, so it has one row per backend: the
|
|
67
|
+
- **Codecs.** Every head-to-head table names the codec of each row. archiver and the ZIP API of
|
|
68
|
+
`node:zlib` run Node's `zlib` module, the host's zlib written in C; jszip (pako) and fflate
|
|
69
|
+
deflate and inflate in JavaScript. zip.js selects its codec at runtime, so it has one row per backend: the
|
|
61
70
|
`CompressionStream` and `DecompressionStream` of the runtime, Node's zlib here and the default
|
|
62
71
|
where they exist; the bundled WebAssembly zlib; and the pure-JavaScript zlib port. The last two
|
|
63
72
|
ship with the library, like the codecs of jszip and fflate, so those rows compare the
|
|
64
73
|
libraries without the host's zlib in the picture.
|
|
65
74
|
- **Checks.** No decompression row verifies the CRC-32 of the entries: zip.js and jszip leave
|
|
66
|
-
the check off by default,
|
|
75
|
+
the check off by default, fflate has none on read, and `node:zlib` verifies by default so its
|
|
76
|
+
row passes `verify: false`. Turning it on in zip.js makes the
|
|
67
77
|
inflater verify the CRC-32 through a gzip trailer: about 7 ms per 20 MB on the WebAssembly
|
|
68
78
|
codec, within the noise on Node's `DecompressionStream`; the pure-JavaScript port keeps a
|
|
69
79
|
separate pass over the output, about 15 ms.
|
|
70
80
|
- **Units.** MB in the tables is 10^6 bytes. The workload sizes (20 MB, 8 MB, 256 MB) and the
|
|
71
81
|
MB/s throughputs use 2^20 bytes.
|
|
72
82
|
|
|
73
|
-
## zip.js against jszip, fflate and
|
|
83
|
+
## zip.js against jszip, fflate, archiver and node:zlib
|
|
74
84
|
|
|
75
85
|
One Node process per measurement (`benchmarks/bench.js`), one thread, every library at its
|
|
76
|
-
level 6. zip.js appears once per codec backend.
|
|
86
|
+
level 6. zip.js appears once per codec backend. The `node:zlib` rows use the ZIP API built into
|
|
87
|
+
Node.js since 26.8.0: `ZipEntry.create()` and `createZipArchive()` to write, `ZipBuffer` to read.
|
|
77
88
|
|
|
78
89
|
### Compression
|
|
79
90
|
|
|
@@ -82,33 +93,37 @@ size each row produced:
|
|
|
82
93
|
|
|
83
94
|
| Library | Codec | Compressible text (20 MB) | Incompressible data (20 MB) | 5,000 files × ~2 KB |
|
|
84
95
|
|---|---|--:|--:|--:|
|
|
85
|
-
| @zip.js/zip.js | `CompressionStream` |
|
|
86
|
-
| @zip.js/zip.js | WASM zlib |
|
|
87
|
-
| @zip.js/zip.js | pure-JS zlib |
|
|
88
|
-
| jszip | pako (JavaScript) |
|
|
89
|
-
| fflate | fflate (JavaScript) |
|
|
90
|
-
| archiver | Node `zlib` (C) |
|
|
96
|
+
| @zip.js/zip.js | `CompressionStream` | 730 ms / 6.1 MB | 357 ms / 21.0 MB | 731 ms / 4.9 MB |
|
|
97
|
+
| @zip.js/zip.js | WASM zlib | 1098 ms / 6.2 MB | 460 ms / 21.0 MB | 551 ms / 4.9 MB |
|
|
98
|
+
| @zip.js/zip.js | pure-JS zlib | 1229 ms / 6.2 MB | 787 ms / 21.0 MB | 962 ms / 4.9 MB |
|
|
99
|
+
| jszip | pako (JavaScript) | 1753 ms / 6.2 MB | 854 ms / 21.0 MB | 846 ms / 4.7 MB |
|
|
100
|
+
| fflate | fflate (JavaScript) | 908 ms / 6.3 MB | **298 ms** / 21.0 MB | **278 ms** / 4.8 MB |
|
|
101
|
+
| archiver | Node `zlib` (C) | 708 ms / 6.1 MB | 359 ms / 21.0 MB | 423 ms / 4.8 MB |
|
|
102
|
+
| node:zlib | Node `zlib` (C) | **678 ms** / 6.1 MB | 346 ms / 21.0 MB | 371 ms / 4.7 MB |
|
|
91
103
|
|
|
92
104
|
Peak memory for the same runs (Δ over baseline):
|
|
93
105
|
|
|
94
106
|
| Library | Codec | Compressible text (20 MB) | Incompressible data (20 MB) | 5,000 files × ~2 KB |
|
|
95
107
|
|---|---|--:|--:|--:|
|
|
96
|
-
| @zip.js/zip.js | `CompressionStream` |
|
|
97
|
-
| @zip.js/zip.js | WASM zlib |
|
|
98
|
-
| @zip.js/zip.js | pure-JS zlib |
|
|
99
|
-
| jszip | pako (JavaScript) |
|
|
100
|
-
| fflate | fflate (JavaScript) |
|
|
101
|
-
| archiver | Node `zlib` (C) |
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
| @zip.js/zip.js | `CompressionStream` | 115 MB | 184 MB | 262 MB |
|
|
109
|
+
| @zip.js/zip.js | WASM zlib | 139 MB | 206 MB | 244 MB |
|
|
110
|
+
| @zip.js/zip.js | pure-JS zlib | 126 MB | 182 MB | 287 MB |
|
|
111
|
+
| jszip | pako (JavaScript) | 76 MB | 102 MB | 269 MB |
|
|
112
|
+
| fflate | fflate (JavaScript) | 68 MB | 114 MB | **106 MB** |
|
|
113
|
+
| archiver | Node `zlib` (C) | 78 MB | 90 MB | 151 MB |
|
|
114
|
+
| node:zlib | Node `zlib` (C) | **51 MB** | **82 MB** | 245 MB |
|
|
115
|
+
|
|
116
|
+
On the text file the three rows that run Node's zlib take the same time within 8 %, for the
|
|
117
|
+
same output size: `node:zlib` 678 ms, archiver 708 ms and zip.js with `CompressionStream`
|
|
118
|
+
730 ms. With the codecs that ship with each library, fflate takes 908 ms for 2 % more bytes,
|
|
119
|
+
zip.js's WebAssembly zlib 1098 ms, its pure-JavaScript port 1229 ms and jszip 1753 ms. On
|
|
120
|
+
incompressible data fflate is the fastest row and `node:zlib` the smallest in memory. On 5,000
|
|
121
|
+
small files the WebAssembly backend is 25 % faster than `CompressionStream`, so a native stream
|
|
122
|
+
costs more per entry than a WebAssembly one; fflate takes 50 % of the WebAssembly row's time
|
|
123
|
+
there against 83 % on the 20 MB file, and the difference is the work zip.js does per entry, a
|
|
124
|
+
stream and a header each. `node:zlib` takes 371 ms on the same files and archiver 423 ms, both
|
|
125
|
+
on the same zlib as `CompressionStream`. The zip.js rows use the most memory on the two 20 MB
|
|
126
|
+
files, where `node:zlib`, which holds one buffer per entry and no stream, uses the least.
|
|
112
127
|
|
|
113
128
|
### Decompression
|
|
114
129
|
|
|
@@ -117,56 +132,59 @@ excluded.
|
|
|
117
132
|
|
|
118
133
|
| Library | Codec | Compressible text (20 MB) | 5,000 files × ~2 KB |
|
|
119
134
|
|---|---|--:|--:|
|
|
120
|
-
| @zip.js/zip.js | `DecompressionStream` |
|
|
121
|
-
| @zip.js/zip.js | WASM zlib |
|
|
122
|
-
| @zip.js/zip.js | pure-JS zlib |
|
|
123
|
-
| jszip | pako (JavaScript) |
|
|
124
|
-
| fflate | fflate (JavaScript) | 116 ms | **
|
|
135
|
+
| @zip.js/zip.js | `DecompressionStream` | 64 ms | 485 ms |
|
|
136
|
+
| @zip.js/zip.js | WASM zlib | 71 ms | 342 ms |
|
|
137
|
+
| @zip.js/zip.js | pure-JS zlib | 155 ms | 527 ms |
|
|
138
|
+
| jszip | pako (JavaScript) | 140 ms | 438 ms |
|
|
139
|
+
| fflate | fflate (JavaScript) | 116 ms | **101 ms** |
|
|
140
|
+
| node:zlib | Node `zlib` (C) | **60 ms** | 183 ms |
|
|
125
141
|
|
|
126
142
|
Peak memory for the same runs (Δ over baseline):
|
|
127
143
|
|
|
128
144
|
| Library | Codec | Compressible text (20 MB) | 5,000 files × ~2 KB |
|
|
129
145
|
|---|---|--:|--:|
|
|
130
|
-
| @zip.js/zip.js | `DecompressionStream` |
|
|
131
|
-
| @zip.js/zip.js | WASM zlib |
|
|
132
|
-
| @zip.js/zip.js | pure-JS zlib |
|
|
133
|
-
| jszip | pako (JavaScript) | 103 MB |
|
|
134
|
-
| fflate | fflate (JavaScript) | **
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
| @zip.js/zip.js | `DecompressionStream` | 151 MB | 355 MB |
|
|
147
|
+
| @zip.js/zip.js | WASM zlib | 171 MB | 358 MB |
|
|
148
|
+
| @zip.js/zip.js | pure-JS zlib | 158 MB | 353 MB |
|
|
149
|
+
| jszip | pako (JavaScript) | 103 MB | 284 MB |
|
|
150
|
+
| fflate | fflate (JavaScript) | **97 MB** | **123 MB** |
|
|
151
|
+
| node:zlib | Node `zlib` (C) | 99 MB | 255 MB |
|
|
152
|
+
|
|
153
|
+
On the 20 MB stream `node:zlib` takes 60 ms and `DecompressionStream` 64 ms, the same zlib
|
|
154
|
+
reached two ways, then the WebAssembly inflate 71 ms, fflate 116 ms, jszip 140 ms and the
|
|
155
|
+
pure-JavaScript port 155 ms. The WebAssembly row includes the module instantiation a fresh
|
|
156
|
+
process pays once, about 20 ms. Warm, the WebAssembly inflate is 27 % faster than
|
|
157
|
+
`DecompressionStream` in the [Codecs](#codecs-at-equal-output-size) table. On 5,000 small files
|
|
158
|
+
the order changes: fflate takes 101 ms, `node:zlib` 183 ms, then the WebAssembly row 342 ms,
|
|
159
|
+
jszip 438 ms, `DecompressionStream` 485 ms and the pure-JavaScript port 527 ms, the same
|
|
160
|
+
per-entry cost as in compression. fflate uses the least memory on both workloads, 35 % of the
|
|
161
|
+
`DecompressionStream` row on the small files. The three zip.js rows allocate the same 800 MB of
|
|
162
|
+
stream objects over the 5,000 entries, about 160 KB per entry, and peak within 5 MB of each
|
|
163
|
+
other.
|
|
148
164
|
|
|
149
165
|
### Streaming a 256 MB file, disk to disk
|
|
150
166
|
|
|
151
167
|
One file read with `fs.createReadStream` in 64 KB chunks and one archive written with
|
|
152
168
|
`fs.createWriteStream`, one entry, each library's streaming API. zip.js takes Web Streams, so its
|
|
153
169
|
rows go through Node's `Readable.toWeb` and `Writable.toWeb` bridges; the other libraries take
|
|
154
|
-
the Node streams directly.
|
|
170
|
+
the Node streams directly, `node:zlib` through `ZipEntry.createStream()`.
|
|
155
171
|
|
|
156
172
|
| Library | Codec | Median time | Peak memory (Δ) | Output |
|
|
157
173
|
|---|---|--:|--:|--:|
|
|
158
|
-
|
|
|
159
|
-
|
|
|
160
|
-
|
|
|
161
|
-
|
|
|
162
|
-
| @zip.js/zip.js |
|
|
163
|
-
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
takes 1.2× fflate's time.
|
|
174
|
+
| node:zlib | Node `zlib` (C) | **8905 ms** | **42 MB** | 78.3 MB |
|
|
175
|
+
| @zip.js/zip.js | `CompressionStream` | 8926 ms | 117 MB | 78.3 MB |
|
|
176
|
+
| archiver | Node `zlib` (C) | 9167 ms | 81 MB | 78.3 MB |
|
|
177
|
+
| fflate | fflate (JavaScript) | 12781 ms | 57 MB | 80.2 MB |
|
|
178
|
+
| @zip.js/zip.js | WASM zlib | 13405 ms | 128 MB | 78.9 MB |
|
|
179
|
+
| @zip.js/zip.js | pure-JS zlib | 15326 ms | 123 MB | 78.9 MB |
|
|
180
|
+
| jszip | pako (JavaScript) | 22809 ms | 83 MB | 78.9 MB |
|
|
181
|
+
|
|
182
|
+
Every row streams: the peaks stay between 42 MB (`node:zlib`) and 128 MB (the WebAssembly zlib)
|
|
183
|
+
over baseline on the 256 MB input. The three rows on Node's zlib take the same time within 3 %;
|
|
184
|
+
jszip takes 2.6× that. With its WebAssembly zlib zip.js takes 1.5× the `CompressionStream` time
|
|
185
|
+
and 1.05× fflate's, for 1.6 % fewer bytes; the pure-JavaScript port takes 1.2× fflate's time.
|
|
186
|
+
The 256 KB chunks zip.js holds at each stage of its pipeline are what separates its
|
|
187
|
+
`CompressionStream` row from `node:zlib` in memory, 117 against 42 MB, at the same speed.
|
|
170
188
|
|
|
171
189
|
## The codec backends of zip.js
|
|
172
190
|
|
|
@@ -187,20 +205,20 @@ fastest such row. A row with an empty last column is beaten by nothing on the ta
|
|
|
187
205
|
|
|
188
206
|
| Codec | Setting | Output | Ratio | Time | Throughput | Beaten by |
|
|
189
207
|
|---|---|--:|--:|--:|--:|---|
|
|
190
|
-
| WASM zlib | level 8 | 6,115,980 | 3.429 |
|
|
191
|
-
| `CompressionStream` | no level control | 6,120,503 | 3.426 |
|
|
192
|
-
| WASM zlib | level 6 | 6,165,103 | 3.402 |
|
|
193
|
-
| fflate | level 8, its best | 6,239,025 | 3.361 |
|
|
194
|
-
| fflate | level 6 | 6,257,881 | 3.351 |
|
|
195
|
-
| WASM zlib | level 5 | 6,562,182 | 3.196 |
|
|
196
|
-
| fflate | level 5 | 6,648,574 | 3.154 |
|
|
197
|
-
| WASM zlib | level 4 | 6,907,987 | 3.036 |
|
|
198
|
-
| fflate | level 1 | 7,261,700 | 2.888 |
|
|
199
|
-
| WASM zlib | level 2 | 7,359,387 | 2.850 |
|
|
200
|
-
| WASM zlib | level 1 | 7,531,042 | 2.785 |
|
|
208
|
+
| WASM zlib | level 8 | 6,115,980 | 3.429 | 1486 ms | 13.5 MB/s | |
|
|
209
|
+
| `CompressionStream` | no level control | 6,120,503 | 3.426 | 695 ms | 28.8 MB/s | |
|
|
210
|
+
| WASM zlib | level 6 | 6,165,103 | 3.402 | 1025 ms | 19.5 MB/s | `CompressionStream` |
|
|
211
|
+
| fflate | level 8, its best | 6,239,025 | 3.361 | 755 ms | 26.5 MB/s | `CompressionStream` |
|
|
212
|
+
| fflate | level 6 | 6,257,881 | 3.351 | 751 ms | 26.6 MB/s | `CompressionStream` |
|
|
213
|
+
| WASM zlib | level 5 | 6,562,182 | 3.196 | 493 ms | 40.6 MB/s | |
|
|
214
|
+
| fflate | level 5 | 6,648,574 | 3.154 | 534 ms | 37.5 MB/s | WASM zlib level 5 |
|
|
215
|
+
| WASM zlib | level 4 | 6,907,987 | 3.036 | 277 ms | 72.3 MB/s | |
|
|
216
|
+
| fflate | level 1 | 7,261,700 | 2.888 | 319 ms | 62.8 MB/s | WASM zlib level 4 |
|
|
217
|
+
| WASM zlib | level 2 | 7,359,387 | 2.850 | 210 ms | 95.0 MB/s | |
|
|
218
|
+
| WASM zlib | level 1 | 7,531,042 | 2.785 | 173 ms | 115.8 MB/s | |
|
|
201
219
|
|
|
202
220
|
The pure-JavaScript port produces the same size as the WebAssembly codec at every level and
|
|
203
|
-
takes 1.0 to 1.
|
|
221
|
+
takes 1.0 to 1.9× its time, the gap closing at the higher levels; the full 28-row table is in
|
|
204
222
|
`benchmarks/results/codecs-results.json`.
|
|
205
223
|
|
|
206
224
|
Every fflate row is beaten by a zlib row: by the host's `CompressionStream` at fflate's levels 6
|
|
@@ -212,16 +230,16 @@ Decompression of the same stream:
|
|
|
212
230
|
|
|
213
231
|
| Codec | Time | Throughput |
|
|
214
232
|
|---|--:|--:|
|
|
215
|
-
| WASM zlib | **
|
|
216
|
-
| `DecompressionStream` |
|
|
217
|
-
| pure-JS zlib |
|
|
218
|
-
| fflate |
|
|
233
|
+
| WASM zlib | **41 ms** | 483.7 MB/s |
|
|
234
|
+
| `DecompressionStream` | 56 ms | 359.1 MB/s |
|
|
235
|
+
| pure-JS zlib | 108 ms | 185.1 MB/s |
|
|
236
|
+
| fflate | 122 ms | 164.4 MB/s |
|
|
219
237
|
|
|
220
|
-
The WebAssembly inflate
|
|
221
|
-
|
|
238
|
+
The WebAssembly inflate is 27 % faster than Node's `DecompressionStream`; fflate's inflate takes
|
|
239
|
+
3.0× the time of the WebAssembly one, the pure-JavaScript port 2.6×.
|
|
222
240
|
|
|
223
|
-
Alone, the WebAssembly backend deflates at level 6 in 1.
|
|
224
|
-
0.7 % more bytes and inflates
|
|
241
|
+
Alone, the WebAssembly backend deflates at level 6 in 1.47× the time of `CompressionStream` for
|
|
242
|
+
0.7 % more bytes and inflates 27 % faster than `DecompressionStream`. zip.js uses it by itself
|
|
225
243
|
when no `CompressionStream` exists or when a level other than the default is requested;
|
|
226
244
|
`useCompressionStream: false` selects it on every host, for an output that does not depend on
|
|
227
245
|
the host's zlib.
|
|
@@ -234,22 +252,28 @@ that buys with each backend.
|
|
|
234
252
|
|
|
235
253
|
| Configuration | Median time | Output | vs jszip |
|
|
236
254
|
|---|--:|--:|--:|
|
|
237
|
-
|
|
|
238
|
-
|
|
|
239
|
-
|
|
|
240
|
-
|
|
|
241
|
-
|
|
|
242
|
-
|
|
|
243
|
-
|
|
|
244
|
-
| zip.js —
|
|
245
|
-
| zip.js —
|
|
246
|
-
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
255
|
+
| node:zlib — `ZipEntry.create()` for every entry at once | **596 ms** | 19.6 MB | **9.6×** |
|
|
256
|
+
| **zip.js — `CompressionStream`, concurrent `add()`** | **602 ms** | 19.6 MB | **9.5×** |
|
|
257
|
+
| fflate — async (its own worker pool) | 757 ms | 20.0 MB | 7.6× |
|
|
258
|
+
| node:zlib — `ZipEntry.create()` one entry at a time | 2209 ms | 19.6 MB | 2.6× |
|
|
259
|
+
| zip.js — `CompressionStream`, sequential `add()` | 2257 ms | 19.6 MB | 2.5× |
|
|
260
|
+
| archiver — Node zlib | 2278 ms | 19.6 MB | 2.5× |
|
|
261
|
+
| fflate — `zipSync` (single thread) | 3191 ms | 20.0 MB | 1.8× |
|
|
262
|
+
| zip.js — WASM zlib, sequential `add()` | 3367 ms | 19.7 MB | 1.7× |
|
|
263
|
+
| zip.js — WASM zlib, concurrent `add()` | 3402 ms | 19.7 MB | 1.7× |
|
|
264
|
+
| zip.js — pure-JS zlib, sequential `add()` | 3868 ms | 19.7 MB | 1.5× |
|
|
265
|
+
| zip.js — pure-JS zlib, concurrent `add()` | 3930 ms | 19.7 MB | 1.5× |
|
|
266
|
+
| jszip (pako, single thread) | 5719 ms | 19.7 MB | 1.0× |
|
|
267
|
+
|
|
268
|
+
With the host's `CompressionStream`, zip.js goes from 2257 ms with sequential `add()` calls to
|
|
269
|
+
602 ms with concurrent ones, 3.7×, without Web Workers: Node runs `CompressionStream` off the
|
|
250
270
|
main thread, so the entries compress on several cores while the JavaScript thread only feeds
|
|
251
|
-
them.
|
|
252
|
-
|
|
271
|
+
them. The ZIP API of `node:zlib` gets the same 3.7× from the same threadpool when its
|
|
272
|
+
`ZipEntry.create()` calls are issued at once, 2209 to 596 ms, the same time as zip.js within
|
|
273
|
+
noise; the difference is in memory, not time: `node:zlib` holds every compressed entry until
|
|
274
|
+
`createZipArchive()` serializes them, while zip.js writes each entry as its codec finishes.
|
|
275
|
+
fflate's async API, which runs its own worker pool, takes 757 ms on the same input; its output
|
|
276
|
+
is 2 % larger than the zlib rows, so it does slightly less work. The WebAssembly and
|
|
253
277
|
pure-JavaScript backends run on the main thread, and concurrent `add()` changes nothing for them.
|
|
254
278
|
|
|
255
279
|
Requesting a non-default level, e.g. `{ level: 5 }`, selects the bundled codec instead of
|
|
@@ -269,22 +293,23 @@ JavaScript thread. Same 8 × 8 MB workload as the backends table, level 6. That
|
|
|
269
293
|
process per run and this one measures in-process after a warmup, which is why their Node numbers
|
|
270
294
|
differ:
|
|
271
295
|
|
|
272
|
-
| Runtime | sequential `add()` | concurrent `add()` | concurrent `add()`, `chunkSize`
|
|
296
|
+
| Runtime | sequential `add()` | concurrent `add()` | concurrent `add()`, `chunkSize` 64 KB | concurrent `add()` + `useWebWorkers` |
|
|
273
297
|
|---|--:|--:|--:|--:|
|
|
274
|
-
| Node.js v26.
|
|
275
|
-
| Bun 1.4.2 | 1.19 s |
|
|
276
|
-
| Deno 2.9.
|
|
298
|
+
| Node.js v26.10.0 | 2.25 s | **0.62 s** | 0.59 s | 0.59 s |
|
|
299
|
+
| Bun 1.4.2 | 1.19 s | **0.26 s** | 1.20 s | 0.27 s |
|
|
300
|
+
| Deno 2.9.7 | 1.35 s | 1.35 s | 1.39 s | **0.37 s** |
|
|
277
301
|
|
|
278
302
|
- **Node** runs `CompressionStream` off the JavaScript thread, on the libuv threadpool, four
|
|
279
|
-
threads by default (`UV_THREADPOOL_SIZE`): concurrent `add()` alone is 3.
|
|
303
|
+
threads by default (`UV_THREADPOOL_SIZE`): concurrent `add()` alone is 3.6× faster than
|
|
280
304
|
sequential, and the chunk size changes nothing. Node has no `Worker` global, so
|
|
281
305
|
`useWebWorkers` spawns nothing there and the entries run in-process, in the same time.
|
|
282
306
|
- **Bun** runs `CompressionStream` off the JavaScript thread only for writes larger than 128 KB
|
|
283
|
-
(its native implementation, since Bun 1.4 in August 2026).
|
|
284
|
-
default,
|
|
285
|
-
|
|
307
|
+
(its native implementation, since Bun 1.4 in August 2026). With the 256 KB chunks zip.js
|
|
308
|
+
writes by default, concurrent `add()` alone is 4.5× faster than sequential; with the 64 KB
|
|
309
|
+
chunks that were the default up to version 2.18.2 it gains nothing, and `useWebWorkers: true`
|
|
310
|
+
is 4.5× faster too.
|
|
286
311
|
- **Deno** runs `CompressionStream` on the JavaScript thread whatever the write size: concurrent
|
|
287
|
-
`add()` alone gains nothing, `useWebWorkers: true` is 3.
|
|
312
|
+
`add()` alone gains nothing, `useWebWorkers: true` is 3.6× faster.
|
|
288
313
|
|
|
289
314
|
### Compression levels
|
|
290
315
|
|
|
@@ -294,13 +319,13 @@ column runs the same JavaScript on each engine. One 20 MB text entry, one thread
|
|
|
294
319
|
|
|
295
320
|
| Runtime | level 6, `CompressionStream` | level 5, WASM zlib | level 5, pure-JS zlib | level 1, WASM zlib |
|
|
296
321
|
|---|--:|--:|--:|--:|
|
|
297
|
-
| Node.js v26.
|
|
298
|
-
| Bun 1.4.2 |
|
|
299
|
-
| Deno 2.9.
|
|
322
|
+
| Node.js v26.10.0 | 691 ms / 6.12 MB | 512 ms / 6.56 MB | 774 ms / 6.56 MB | 186 ms / 7.53 MB |
|
|
323
|
+
| Bun 1.4.2 | 430 ms / 6.20 MB | 465 ms / 6.56 MB | 665 ms / 6.56 MB | 166 ms / 7.53 MB |
|
|
324
|
+
| Deno 2.9.7 | 409 ms / 6.20 MB | 592 ms / 6.56 MB | 682 ms / 6.56 MB | 236 ms / 7.53 MB |
|
|
300
325
|
|
|
301
|
-
On Node, level 5 on the WebAssembly codec is
|
|
326
|
+
On Node, level 5 on the WebAssembly codec is 26 % faster than the default for 7 % more bytes.
|
|
302
327
|
On Bun and Deno the default is already faster than level 5, so level 5 there is slower and
|
|
303
|
-
larger; only level 1 buys speed on them,
|
|
328
|
+
larger; only level 1 buys speed on them, 61 % on Bun and 42 % on Deno, at 21 % more bytes. The
|
|
304
329
|
pure-JavaScript port at level 5 takes 1.5× the WebAssembly time on Node, 1.4× on Bun and 1.2×
|
|
305
330
|
on Deno, and is slower than the default on every runtime. The two bundled codecs produce the
|
|
306
331
|
same size on every runtime; the level-6 sizes differ because they come from three different
|
|
@@ -310,17 +335,17 @@ zlib builds.
|
|
|
310
335
|
|
|
311
336
|
On bulk data zip.js adds almost nothing over the host's `CompressionStream`, so its throughput is
|
|
312
337
|
that of the zlib the runtime ships, and they differ. One 256 MB text file, one thread, in
|
|
313
|
-
memory, fed in
|
|
338
|
+
memory, fed in 256 KB writes as zip.js does; gzip is Apple's, timed as a child process:
|
|
314
339
|
|
|
315
340
|
| Runtime | `CompressionStream` alone | zip.js, one entry | Output |
|
|
316
341
|
|---|--:|--:|--:|
|
|
317
|
-
| Node.js v26.
|
|
318
|
-
| Bun 1.4.2 | 4.
|
|
319
|
-
| Deno 2.9.
|
|
320
|
-
| Apple `gzip -6`, classic zlib | 12.
|
|
342
|
+
| Node.js v26.10.0 | 9.06 s | 9.10 s | 78.3 MB |
|
|
343
|
+
| Bun 1.4.2 | 4.74 s | 4.85 s | 79.4 MB |
|
|
344
|
+
| Deno 2.9.7 | 5.22 s | 5.21 s | 79.4 MB |
|
|
345
|
+
| Apple `gzip -6`, classic zlib | 12.4 to 12.5 s | | 78.9 MB |
|
|
321
346
|
|
|
322
|
-
zip.js is within
|
|
323
|
-
takes
|
|
347
|
+
zip.js is within 3 % of the raw stream on every runtime. On this file Bun's `CompressionStream`
|
|
348
|
+
takes 52 % of Node's time and Deno's 58 %, for about 1.4 % more bytes; Apple's gzip takes 1.4×
|
|
324
349
|
Node's time. Which zlib each runtime ships is not verified here; the table measures the result.
|
|
325
350
|
|
|
326
351
|
## Encryption: AES-256 on a stored entry
|
|
@@ -334,29 +359,29 @@ one archive written, then read back.
|
|
|
334
359
|
|
|
335
360
|
| Engine | Node.js | Bun | Deno |
|
|
336
361
|
|---|--:|--:|--:|
|
|
337
|
-
| WebAssembly, linked into the module of the WebAssembly builds | **
|
|
338
|
-
| JavaScript, the fallback of those builds and the engine of the native and core builds |
|
|
339
|
-
| 2.13.1, sjcl |
|
|
362
|
+
| WebAssembly, linked into the module of the WebAssembly builds | **134 / 135 MB/s** | **119 / 121** | 124 / 124 |
|
|
363
|
+
| JavaScript, the fallback of those builds and the engine of the native and core builds | 119 / 108 | 115 / 117 | **144 / 133** |
|
|
364
|
+
| 2.13.1, sjcl (measured on 2026-09-26, Node.js v26.7.0) | 26 / 26 | 28 / 28 | 20 / 19 |
|
|
340
365
|
|
|
341
366
|
The same measurement in a browser, through the Web Worker pool, on 32 MB of bytes generated in
|
|
342
367
|
the page (the corpus is not served to the browser), median of 3 passes
|
|
343
368
|
([`benchmarks/bench-aes.html`](benchmarks/bench-aes.html), which prints its result and writes no
|
|
344
369
|
file):
|
|
345
370
|
|
|
346
|
-
| Engine | Firefox
|
|
371
|
+
| Engine | Firefox 156 | Chrome 153 |
|
|
347
372
|
|---|--:|--:|
|
|
348
|
-
| WebAssembly | **
|
|
349
|
-
| JavaScript |
|
|
373
|
+
| WebAssembly | **125 / 125 MB/s** | **136 / 138** |
|
|
374
|
+
| JavaScript | 79 / 79 | 104 / 104 |
|
|
350
375
|
| 2.13.1, sjcl | 17 / 17 | 22 / 22 |
|
|
351
376
|
|
|
352
|
-
- The JavaScript engine is 4.
|
|
353
|
-
7.
|
|
377
|
+
- The JavaScript engine is 4.1 to 4.7× sjcl on Node, Bun and in the two browsers, and 7.0 to
|
|
378
|
+
7.2× on Deno, where sjcl was slowest. sjcl ran the cipher 16 bytes
|
|
354
379
|
at a time through a bit-array layer; the new engine works on typed arrays and is fed whole
|
|
355
380
|
chunks, with the AES rounds and the SHA-1 steps written out. Every build has it.
|
|
356
381
|
- The WebAssembly kernel is the same C code on every host, with the same rounds written out:
|
|
357
|
-
|
|
358
|
-
engine is within
|
|
359
|
-
the browsers. The WebAssembly builds use
|
|
382
|
+
119 to 135 MB/s on Node, Bun and Deno, 125 to 138 MB/s in the two browsers. The JavaScript
|
|
383
|
+
engine is within 4 % of it on Bun and 20 % on Node, ahead of it on Deno, and 1.3 to 1.6×
|
|
384
|
+
slower in the browsers. The WebAssembly builds use
|
|
360
385
|
the kernel whenever their module loads and fall back to the JavaScript engine when it cannot,
|
|
361
386
|
for instance under a Content Security Policy that does not allow WebAssembly
|
|
362
387
|
(`'wasm-unsafe-eval'`).
|
|
@@ -369,8 +394,8 @@ file):
|
|
|
369
394
|
bytes in 2.13.1 to 67,820 in 2.14.0, `dist/zip-native.min.js` from 73,798 to 72,060 and
|
|
370
395
|
`dist/zip-core.min.js` from 36,405 to 35,493 (`git show <tag>:<file> | gzip -9 | wc -c`):
|
|
371
396
|
sjcl's removal outweighs the JavaScript engine and the kernel. Writing the rounds out after
|
|
372
|
-
2.14.0
|
|
373
|
-
|
|
397
|
+
2.14.0 gave some of it back, and the three files behind the encryption tables above gzip to
|
|
398
|
+
73,184, 75,050 and 37,264 bytes.
|
|
374
399
|
|
|
375
400
|
## Reproduce
|
|
376
401
|
|
|
@@ -379,7 +404,7 @@ The harness lives in [`benchmarks/`](benchmarks/). `bench.js` and `bench-backend
|
|
|
379
404
|
|
|
380
405
|
```sh
|
|
381
406
|
cd benchmarks
|
|
382
|
-
npm install # jszip, fflate, archiver (zip.js is used from the repository)
|
|
407
|
+
npm install # jszip, fflate, archiver (zip.js is used from the repository, node:zlib is built in)
|
|
383
408
|
npm run corpus # generate the datasets under .corpus/
|
|
384
409
|
node bench.js # the head-to-head tables: compress, decompress, disk streaming
|
|
385
410
|
node bench-backends.js # the concurrent add() and backends table
|
|
@@ -390,7 +415,10 @@ node bench-runtimes.js # the runtime tables; also: bun bench-runtimes.js, deno
|
|
|
390
415
|
|
|
391
416
|
Each script writes its JSON to `benchmarks/results/`; the `*-run.log` files there are the
|
|
392
417
|
console output of the runs behind this page. `RUNS=<n>` sets the number of repetitions (default
|
|
393
|
-
3, 5 in `bench-aes.js`). `
|
|
418
|
+
3, 5 in `bench-aes.js`). `QUICK=1` divides every workload by 8, runs each measurement once and
|
|
419
|
+
writes under `benchmarks/results/quick/`; the scripts then finish in seconds instead of minutes,
|
|
420
|
+
which is enough to check that they run and not enough to read a number from.
|
|
421
|
+
`ZIPJS_BUNDLE=<path to a previous index.min.js> node bench-aes.js` adds
|
|
394
422
|
the row of a previous release; `git show v2.13.1:index.min.js` gave the sjcl row. The browser
|
|
395
423
|
encryption table comes from `benchmarks/bench-aes.html`, served from the repository root
|
|
396
424
|
(`npx http-server -p 8080`, then `http://localhost:8080/benchmarks/bench-aes.html`), with
|