mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
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:
parent
4ed9d21880
commit
c85460b0ad
47
benchmark/zlib/crc32.js
Normal file
47
benchmark/zlib/crc32.js
Normal 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('');
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
26
test/sequential/test-zlib-crc32-fast-api.js
Normal file
26
test/sequential/test-zlib-crc32-fast-api.js
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user