Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions benchmark/zlib/crc32.js
Original file line number Diff line number Diff line change
@@ -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('');
}
37 changes: 27 additions & 10 deletions src/node_zlib.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
#include "threadpoolwork-inl.h"
#include "util-inl.h"

#include "node_debug.h"
#include "v8-fast-api-calls.h"
#include "v8.h"

#include "brotli/decode.h"
Expand All @@ -48,6 +50,7 @@
namespace node {

using v8::ArrayBuffer;
using v8::CFunction;
using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
Expand Down Expand Up @@ -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) {
CHECK(args[0]->IsArrayBufferView() || args[0]->IsString());
CHECK(args[1]->IsUint32());
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,
Local<Value> unused,
Local<Context> context,
Expand All @@ -1685,7 +1701,7 @@ void Initialize(Local<Object> target,
MakeClass<ZstdCompressStream>::Make(env, target, "ZstdCompress");
MakeClass<ZstdDecompressStream>::Make(env, target, "ZstdDecompress");

SetMethod(context, target, "crc32", CRC32);
SetFastMethodNoSideEffect(context, target, "crc32", CRC32, &fast_crc32_);
target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "ZLIB_VERSION"),
FIXED_ONE_BYTE_STRING(env->isolate(), ZLIB_VERSION)).Check();
Expand All @@ -1698,6 +1714,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
MakeClass<ZstdCompressStream>::Make(registry);
MakeClass<ZstdDecompressStream>::Make(registry);
registry->Register(CRC32);
registry->Register(fast_crc32_);
}

} // anonymous namespace
Expand Down
26 changes: 26 additions & 0 deletions test/sequential/test-zlib-crc32-fast-api.js
Original file line number Diff line number Diff line change
@@ -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