zlib: implement fast path for crc32

PR-URL: https://github.com/nodejs/node/pull/59813
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Daniel Lemire <daniel@lemire.me>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
This commit is contained in:
Gürgün Dayıoğlu 2025-09-14 02:39:58 +02:00 committed by GitHub
parent 4ed9d21880
commit c85460b0ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 101 additions and 11 deletions

47
benchmark/zlib/crc32.js Normal file
View File

@ -0,0 +1,47 @@
'use strict';
const common = require('../common.js');
const { crc32 } = require('zlib');
// Benchmark crc32 on Buffer and String inputs across sizes.
// Iteration count is scaled inversely with input length to keep runtime sane.
// Example:
// node benchmark/zlib/crc32.js type=buffer len=4096 n=4000000
// ./out/Release/node benchmark/zlib/crc32.js --test
const bench = common.createBenchmark(main, {
type: ['buffer', 'string'],
len: [32, 256, 4096, 65536],
n: [4e6],
});
function makeBuffer(size) {
const buf = Buffer.allocUnsafe(size);
for (let i = 0; i < size; i++) buf[i] = (i * 1103515245 + 12345) & 0xff;
return buf;
}
function makeAsciiString(size) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
let out = '';
for (let i = 0, j = 0; i < size; i++, j = (j + 7) % chars.length) out += chars[j];
return out;
}
function main({ type, len, n }) {
// Scale iterations so that total processed bytes roughly constant around n*4096 bytes.
const scale = 4096 / len;
const iters = Math.max(1, Math.floor(n * scale));
const data = type === 'buffer' ? makeBuffer(len) : makeAsciiString(len);
let acc = 0;
for (let i = 0; i < Math.min(iters, 10000); i++) acc ^= crc32(data, 0);
bench.start();
let sum = 0;
for (let i = 0; i < iters; i++) sum ^= crc32(data, 0);
bench.end(iters);
if (sum === acc - 1) process.stderr.write('');
}

View File

@ -30,6 +30,8 @@
#include "threadpoolwork-inl.h" #include "threadpoolwork-inl.h"
#include "util-inl.h" #include "util-inl.h"
#include "node_debug.h"
#include "v8-fast-api-calls.h"
#include "v8.h" #include "v8.h"
#include "brotli/decode.h" #include "brotli/decode.h"
@ -48,6 +50,7 @@
namespace node { namespace node {
using v8::ArrayBuffer; using v8::ArrayBuffer;
using v8::CFunction;
using v8::Context; using v8::Context;
using v8::Function; using v8::Function;
using v8::FunctionCallbackInfo; using v8::FunctionCallbackInfo;
@ -1657,22 +1660,35 @@ T CallOnSequence(v8::Isolate* isolate, Local<Value> value, F callback) {
} }
} }
// TODO(joyeecheung): use fast API static inline uint32_t CRC32Impl(Isolate* isolate,
Local<Value> data,
uint32_t value) {
return CallOnSequence<uint32_t>(
isolate, data, [&](const char* ptr, size_t size) -> uint32_t {
return static_cast<uint32_t>(
crc32(value, reinterpret_cast<const Bytef*>(ptr), size));
});
}
static void CRC32(const FunctionCallbackInfo<Value>& args) { static void CRC32(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsArrayBufferView() || args[0]->IsString()); CHECK(args[0]->IsArrayBufferView() || args[0]->IsString());
CHECK(args[1]->IsUint32()); CHECK(args[1]->IsUint32());
uint32_t value = args[1].As<v8::Uint32>()->Value(); uint32_t value = args[1].As<v8::Uint32>()->Value();
args.GetReturnValue().Set(CRC32Impl(args.GetIsolate(), args[0], value));
uint32_t result = CallOnSequence<uint32_t>(
args.GetIsolate(),
args[0],
[&](const char* data, size_t size) -> uint32_t {
return crc32(value, reinterpret_cast<const Bytef*>(data), size);
});
args.GetReturnValue().Set(result);
} }
static uint32_t FastCRC32(v8::Local<v8::Value> receiver,
v8::Local<v8::Value> data,
uint32_t value,
// NOLINTNEXTLINE(runtime/references)
v8::FastApiCallbackOptions& options) {
TRACK_V8_FAST_API_CALL("zlib.crc32");
v8::HandleScope handle_scope(options.isolate);
return CRC32Impl(options.isolate, data, value);
}
static CFunction fast_crc32_(CFunction::Make(FastCRC32));
void Initialize(Local<Object> target, void Initialize(Local<Object> target,
Local<Value> unused, Local<Value> unused,
Local<Context> context, Local<Context> context,
@ -1685,7 +1701,7 @@ void Initialize(Local<Object> target,
MakeClass<ZstdCompressStream>::Make(env, target, "ZstdCompress"); MakeClass<ZstdCompressStream>::Make(env, target, "ZstdCompress");
MakeClass<ZstdDecompressStream>::Make(env, target, "ZstdDecompress"); MakeClass<ZstdDecompressStream>::Make(env, target, "ZstdDecompress");
SetMethod(context, target, "crc32", CRC32); SetFastMethodNoSideEffect(context, target, "crc32", CRC32, &fast_crc32_);
target->Set(env->context(), target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "ZLIB_VERSION"), FIXED_ONE_BYTE_STRING(env->isolate(), "ZLIB_VERSION"),
FIXED_ONE_BYTE_STRING(env->isolate(), ZLIB_VERSION)).Check(); FIXED_ONE_BYTE_STRING(env->isolate(), ZLIB_VERSION)).Check();
@ -1698,6 +1714,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
MakeClass<ZstdCompressStream>::Make(registry); MakeClass<ZstdCompressStream>::Make(registry);
MakeClass<ZstdDecompressStream>::Make(registry); MakeClass<ZstdDecompressStream>::Make(registry);
registry->Register(CRC32); registry->Register(CRC32);
registry->Register(fast_crc32_);
} }
} // anonymous namespace } // anonymous namespace

View File

@ -0,0 +1,26 @@
// Flags: --expose-internals --no-warnings --allow-natives-syntax
'use strict';
const common = require('../common');
const assert = require('assert');
const zlib = require('zlib');
{
function testFastPath() {
const expected = 0xd87f7e0c; // zlib.crc32('test', 0)
assert.strictEqual(zlib.crc32('test', 0), expected);
return expected;
}
eval('%PrepareFunctionForOptimization(zlib.crc32)');
testFastPath();
eval('%OptimizeFunctionOnNextCall(zlib.crc32)');
testFastPath();
testFastPath();
if (common.isDebug) {
const { internalBinding } = require('internal/test/binding');
const { getV8FastApiCallCount } = internalBinding('debug');
assert.strictEqual(getV8FastApiCallCount('zlib.crc32'), 2);
}
}