Skip to content

Commit cec66fb

Browse files
committed
Add nodejs zlib binary compatibility and update benchmark
1 parent e78929a commit cec66fb

9 files changed

Lines changed: 257 additions & 217 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@ jobs:
1515

1616
strategy:
1717
matrix:
18-
# This is last node with original zlib.
19-
# It's required to test deflate binary equality.
20-
node-version: [ '12.16' ]
18+
node-version: [ '24' ]
2119

2220
steps:
2321
- uses: actions/checkout@v2

README.md

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,16 @@ develop native C modules for CPU-intensive tasks. Enjoy the result!
1919
__Benchmarks:__
2020

2121

22-
node v12.16.3 (zlib 1.2.9), 1mb input sample:
22+
node v24, 1mb input sample:
2323

2424
```
25-
deflate-imaya x 4.75 ops/sec ±4.93% (15 runs sampled)
26-
deflate-pako x 10.38 ops/sec ±0.37% (29 runs sampled)
27-
deflate-zlib x 17.74 ops/sec ±0.77% (46 runs sampled)
28-
gzip-pako x 8.86 ops/sec ±1.41% (29 runs sampled)
29-
inflate-imaya x 107 ops/sec ±0.69% (77 runs sampled)
30-
inflate-pako x 131 ops/sec ±1.74% (82 runs sampled)
31-
inflate-zlib x 258 ops/sec ±0.66% (88 runs sampled)
32-
ungzip-pako x 115 ops/sec ±1.92% (80 runs sampled)
33-
```
34-
35-
node v14.15.0 (google's zlib), 1mb output sample:
36-
37-
```
38-
deflate-imaya x 4.93 ops/sec ±3.09% (16 runs sampled)
39-
deflate-pako x 10.22 ops/sec ±0.33% (29 runs sampled)
40-
deflate-zlib x 18.48 ops/sec ±0.24% (48 runs sampled)
41-
gzip-pako x 10.16 ops/sec ±0.25% (28 runs sampled)
42-
inflate-imaya x 110 ops/sec ±0.41% (77 runs sampled)
43-
inflate-pako x 134 ops/sec ±0.66% (83 runs sampled)
44-
inflate-zlib x 402 ops/sec ±0.74% (87 runs sampled)
45-
ungzip-pako x 113 ops/sec ±0.62% (80 runs sampled)
25+
deflate-pako x 14.27 ops/sec ±3.41% (37 runs sampled)
26+
deflate-pako-zlib-hash x 10.60 ops/sec ±0.50% (29 runs sampled)
27+
deflate-zlib x 30.30 ops/sec ±0.61% (51 runs sampled)
28+
gzip-pako x 13.48 ops/sec ±0.50% (36 runs sampled)
29+
inflate-pako x 138 ops/sec ±1.26% (75 runs sampled)
30+
inflate-zlib x 397 ops/sec ±1.37% (81 runs sampled)
31+
ungzip-pako x 125 ops/sec ±1.46% (73 runs sampled)
4632
```
4733

4834
zlib's test is partially affected by marshalling (that make sense for inflate only).
@@ -143,14 +129,6 @@ Pako does not contain some specific zlib functions:
143129
modes.
144130

145131

146-
pako for enterprise
147-
-------------------
148-
149-
Available as part of the Tidelift Subscription
150-
151-
The maintainers of pako and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-pako?utm_source=npm-pako&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
152-
153-
154132
Authors
155133
-------
156134

benchmark/implementations/deflate-imaya/index.js

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use strict';
2+
3+
const pako = require('../../../');
4+
5+
// Pako with the classic zlib hash (legacyHash), instead of fast ANZAC++.
6+
exports.run = (data, level) => {
7+
return pako.deflate(data.typed, { level: level, legacyHash: true });
8+
};

benchmark/implementations/inflate-imaya/index.js

Lines changed: 0 additions & 8 deletions
This file was deleted.

benchmark/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
"version": "0.0.0",
44
"dependencies": {
55
"ansi": "^0.3.1",
6-
"benchmark": "^2.1.4",
7-
"zlibjs": "^0.3.1"
6+
"benchmark": "^2.1.4"
87
}
98
}

lib/deflate.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ const {
7777
* [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
7878
* for more information on these.
7979
*
80+
* - `legacyHash` (Boolean) - use the classic zlib hash instead of the default
81+
* ANZAC++ hash. The default matches node.js output; enable to match canonical
82+
* zlib output instead.
83+
*
8084
* Additional options, for internal needs:
8185
*
8286
* - `chunkSize` - size of generated data chunks (16K by default)
@@ -115,7 +119,8 @@ function Deflate(options) {
115119
chunkSize: 16384,
116120
windowBits: 15,
117121
memLevel: 8,
118-
strategy: Z_DEFAULT_STRATEGY
122+
strategy: Z_DEFAULT_STRATEGY,
123+
legacyHash: false
119124
}, options || {});
120125

121126
let opt = this.options;
@@ -142,7 +147,8 @@ function Deflate(options) {
142147
opt.method,
143148
opt.windowBits,
144149
opt.memLevel,
145-
opt.strategy
150+
opt.strategy,
151+
opt.legacyHash
146152
);
147153

148154
if (status !== Z_OK) {

lib/zlib/deflate.js

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,21 @@ let HASH = HASH_ZLIB;
144144
* the last MIN_MATCH-1 bytes of the input file).
145145
*/
146146
const INSERT_STRING = (s, str) => {
147-
/* UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]); */
148-
s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]);
149-
const hash_head = s.prev[str & s.w_mask] = s.head[s.ins_h];
150-
s.head[s.ins_h] = str;
147+
let h;
148+
if (s.legacy_hash) {
149+
/* UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]); */
150+
h = s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]);
151+
} else {
152+
// ANZAC++ hash: reads 4 bytes, matches node.js zlib output (legacyHash
153+
// restores classic zlib hash). Faster, with fewer collisions.
154+
const w = s.window;
155+
// Read 4 bytes little-endian. Math.imul reproduces C uint32 overflow in
156+
// `(value * 66521 + 66521) >> 16` exactly.
157+
const value = w[str] | (w[str + 1] << 8) | (w[str + 2] << 16) | (w[str + 3] << 24);
158+
h = s.ins_h = ((Math.imul(value, 66521) + 66521) >>> 16) & s.hash_mask;
159+
}
160+
const hash_head = s.prev[str & s.w_mask] = s.head[h];
161+
s.head[h] = str;
151162
return hash_head;
152163
};
153164

@@ -423,7 +434,20 @@ const fill_window = (s) => {
423434
s.lookahead += n;
424435

425436
/* Initialize the hash value now that we have some input: */
426-
if (s.lookahead + s.insert >= MIN_MATCH) {
437+
if (!s.legacy_hash) {
438+
/* The 4-byte hash reads one extra byte, so it needs one more available. */
439+
if (s.lookahead + s.insert > MIN_MATCH) {
440+
str = s.strstart - s.insert;
441+
while (s.insert) {
442+
INSERT_STRING(s, str);
443+
str++;
444+
s.insert--;
445+
if (s.lookahead + s.insert <= MIN_MATCH) {
446+
break;
447+
}
448+
}
449+
}
450+
} else if (s.lookahead + s.insert >= MIN_MATCH) {
427451
str = s.strstart - s.insert;
428452
s.ins_h = s.window[str];
429453

@@ -433,11 +457,7 @@ const fill_window = (s) => {
433457
// Call update_hash() MIN_MATCH-3 more times
434458
//#endif
435459
while (s.insert) {
436-
/* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
437-
s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]);
438-
439-
s.prev[str & s.w_mask] = s.head[s.ins_h];
440-
s.head[s.ins_h] = str;
460+
INSERT_STRING(s, str);
441461
str++;
442462
s.insert--;
443463
if (s.lookahead + s.insert < MIN_MATCH) {
@@ -775,16 +795,18 @@ const deflate_fast = (s, flush) => {
775795
{
776796
s.strstart += s.match_length;
777797
s.match_length = 0;
778-
s.ins_h = s.window[s.strstart];
779-
/* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */
780-
s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + 1]);
798+
if (s.legacy_hash) {
799+
s.ins_h = s.window[s.strstart];
800+
/* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */
801+
s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + 1]);
781802

782803
//#if MIN_MATCH != 3
783804
// Call UPDATE_HASH() MIN_MATCH-3 more times
784805
//#endif
785-
/* If lookahead < MIN_MATCH, ins_h is garbage, but it does not
786-
* matter since it will be recomputed at next deflate call.
787-
*/
806+
/* If lookahead < MIN_MATCH, ins_h is garbage, but it does not
807+
* matter since it will be recomputed at next deflate call.
808+
*/
809+
}
788810
}
789811
} else {
790812
/* No match, output a literal byte */
@@ -1230,6 +1252,7 @@ function DeflateState() {
12301252
this.head = null; /* Heads of the hash chains or NIL. */
12311253

12321254
this.ins_h = 0; /* hash index of string to be inserted */
1255+
this.legacy_hash = 0; /* use classic zlib hash instead of default ANZAC++ */
12331256
this.hash_size = 0; /* number of elements in hash table */
12341257
this.hash_bits = 0; /* log2(hash_size) */
12351258
this.hash_mask = 0; /* hash_size-1 */
@@ -1452,7 +1475,7 @@ const deflateSetHeader = (strm, head) => {
14521475
};
14531476

14541477

1455-
const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy) => {
1478+
const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy, legacyHash) => {
14561479

14571480
if (!strm) { // === Z_NULL
14581481
return Z_STREAM_ERROR;
@@ -1498,7 +1521,13 @@ const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy) => {
14981521
s.w_size = 1 << s.w_bits;
14991522
s.w_mask = s.w_size - 1;
15001523

1524+
s.legacy_hash = legacyHash ? 1 : 0;
1525+
15011526
s.hash_bits = memLevel + 7;
1527+
/* ANZAC++ hash needs >= 15 hash bits to span its 4 read bytes. */
1528+
if (!s.legacy_hash && s.hash_bits < 15) {
1529+
s.hash_bits = 15;
1530+
}
15021531
s.hash_size = 1 << s.hash_bits;
15031532
s.hash_mask = s.hash_size - 1;
15041533
s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH);
@@ -2002,12 +2031,7 @@ const deflateSetDictionary = (strm, dictionary) => {
20022031
let str = s.strstart;
20032032
let n = s.lookahead - (MIN_MATCH - 1);
20042033
do {
2005-
/* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
2006-
s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]);
2007-
2008-
s.prev[str & s.w_mask] = s.head[s.ins_h];
2009-
2010-
s.head[s.ins_h] = str;
2034+
INSERT_STRING(s, str);
20112035
str++;
20122036
} while (--n);
20132037
s.strstart = str;

0 commit comments

Comments
 (0)