Skip to content

Commit a71d5b6

Browse files
committed
zlib: implement fast path for crc32
1 parent a7fde8a commit a71d5b6

File tree

2 files changed

+73
-2
lines changed

2 files changed

+73
-2
lines changed

benchmark/zlib/crc32.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const { crc32 } = require('zlib');
5+
6+
// Benchmark crc32 on Buffer and String inputs across sizes.
7+
// Iteration count is scaled inversely with input length to keep runtime sane.
8+
// Example:
9+
// node benchmark/zlib/crc32.js type=buffer len=4096 n=4000000
10+
// ./out/Release/node benchmark/zlib/crc32.js --test
11+
12+
const bench = common.createBenchmark(main, {
13+
type: ['buffer', 'string'],
14+
len: [32, 256, 4096, 65536],
15+
n: [4e6],
16+
});
17+
18+
function makeBuffer(size) {
19+
const buf = Buffer.allocUnsafe(size);
20+
for (let i = 0; i < size; i++) buf[i] = (i * 1103515245 + 12345) & 0xff;
21+
return buf;
22+
}
23+
24+
function makeAsciiString(size) {
25+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
26+
let out = '';
27+
for (let i = 0, j = 0; i < size; i++, j = (j + 7) % chars.length) out += chars[j];
28+
return out;
29+
}
30+
31+
function main({ type, len, n }) {
32+
// Scale iterations so that total processed bytes roughly constant around n*4096 bytes.
33+
const scale = 4096 / len;
34+
const iters = Math.max(1, Math.floor(n * scale));
35+
36+
const data = type === 'buffer' ? makeBuffer(len) : makeAsciiString(len);
37+
38+
let acc = 0;
39+
for (let i = 0; i < Math.min(iters, 10000); i++) acc ^= crc32(data, 0);
40+
41+
bench.start();
42+
let sum = 0;
43+
for (let i = 0; i < iters; i++) sum ^= crc32(data, 0);
44+
bench.end(iters);
45+
46+
if (sum === acc - 1) process.stderr.write('');
47+
}

src/node_zlib.cc

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
#include "threadpoolwork-inl.h"
3131
#include "util-inl.h"
3232

33+
#include "node_debug.h"
34+
#include "v8-fast-api-calls.h"
3335
#include "v8.h"
3436

3537
#include "brotli/decode.h"
@@ -48,6 +50,7 @@
4850
namespace node {
4951

5052
using v8::ArrayBuffer;
53+
using v8::CFunction;
5154
using v8::Context;
5255
using v8::Function;
5356
using v8::FunctionCallbackInfo;
@@ -1657,7 +1660,6 @@ T CallOnSequence(v8::Isolate* isolate, Local<Value> value, F callback) {
16571660
}
16581661
}
16591662

1660-
// TODO(joyeecheung): use fast API
16611663
static void CRC32(const FunctionCallbackInfo<Value>& args) {
16621664
CHECK(args[0]->IsArrayBufferView() || args[0]->IsString());
16631665
CHECK(args[1]->IsUint32());
@@ -1673,6 +1675,27 @@ static void CRC32(const FunctionCallbackInfo<Value>& args) {
16731675
args.GetReturnValue().Set(result);
16741676
}
16751677

1678+
static uint32_t FastCRC32(v8::Local<v8::Value> receiver,
1679+
v8::Local<v8::Value> data,
1680+
uint32_t value,
1681+
// NOLINTNEXTLINE(runtime/references)
1682+
v8::FastApiCallbackOptions& options) {
1683+
TRACK_V8_FAST_API_CALL("zlib.crc32");
1684+
v8::HandleScope handle_scope(options.isolate);
1685+
CHECK(data->IsArrayBufferView() || data->IsString());
1686+
if (data->IsArrayBufferView()) {
1687+
SPREAD_BUFFER_ARG(data, buf);
1688+
return static_cast<uint32_t>(
1689+
crc32(value, reinterpret_cast<const Bytef*>(buf_data), buf_length));
1690+
}
1691+
v8::Local<v8::String> s = data.As<v8::String>();
1692+
Utf8Value utf8(options.isolate, s);
1693+
return static_cast<uint32_t>(
1694+
crc32(value, reinterpret_cast<const Bytef*>(utf8.out()), utf8.length()));
1695+
}
1696+
1697+
static CFunction fast_crc32_(CFunction::Make(FastCRC32));
1698+
16761699
void Initialize(Local<Object> target,
16771700
Local<Value> unused,
16781701
Local<Context> context,
@@ -1685,7 +1708,7 @@ void Initialize(Local<Object> target,
16851708
MakeClass<ZstdCompressStream>::Make(env, target, "ZstdCompress");
16861709
MakeClass<ZstdDecompressStream>::Make(env, target, "ZstdDecompress");
16871710

1688-
SetMethod(context, target, "crc32", CRC32);
1711+
SetFastMethodNoSideEffect(context, target, "crc32", CRC32, &fast_crc32_);
16891712
target->Set(env->context(),
16901713
FIXED_ONE_BYTE_STRING(env->isolate(), "ZLIB_VERSION"),
16911714
FIXED_ONE_BYTE_STRING(env->isolate(), ZLIB_VERSION)).Check();
@@ -1698,6 +1721,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
16981721
MakeClass<ZstdCompressStream>::Make(registry);
16991722
MakeClass<ZstdDecompressStream>::Make(registry);
17001723
registry->Register(CRC32);
1724+
registry->Register(fast_crc32_);
17011725
}
17021726

17031727
} // anonymous namespace

0 commit comments

Comments
 (0)