GeezerVison is showing github.com — zlib: implement fast path for crc32 · nodejs/node@9782ca2
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboard +47Lines changed: 47 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +} |
Collapse file Copy file name to clipboardExpand all lines: src/node_zlib.cc +27-10Lines changed: 27 additions & 10 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 30 | 30 | #include "threadpoolwork-inl.h" | 31 | 31 | #include "util-inl.h" | 32 | 32 |
| | 33 | +#include "node_debug.h" | | 34 | +#include "v8-fast-api-calls.h" | 33 | 35 | #include "v8.h" | 34 | 36 |
| 35 | 37 | #include "brotli/decode.h" | | 48 | 50 | namespace node { | 49 | 51 |
| 50 | 52 | using v8::ArrayBuffer; | | 53 | +using v8::CFunction; | 51 | 54 | using v8::Context; | 52 | 55 | using v8::Function; | 53 | 56 | using v8::FunctionCallbackInfo; | @@ -1657,22 +1660,35 @@ T CallOnSequence(v8::Isolate* isolate, Local<Value> value, F callback) { | 1657 | 1660 | } | 1658 | 1661 | } | 1659 | 1662 |
| 1660 | | -// TODO(joyeecheung): use fast API | | 1663 | +static inline uint32_t CRC32Impl(Isolate* isolate, | | 1664 | + Local<Value> data, | | 1665 | + uint32_t value) { | | 1666 | + return CallOnSequence<uint32_t>( | | 1667 | + isolate, data, [&](const char* ptr, size_t size) -> uint32_t { | | 1668 | + return static_cast<uint32_t>( | | 1669 | + crc32(value, reinterpret_cast<const Bytef*>(ptr), size)); | | 1670 | + }); | | 1671 | +} | | 1672 | + | 1661 | 1673 | static void CRC32(const FunctionCallbackInfo<Value>& args) { | 1662 | 1674 | CHECK(args[0]->IsArrayBufferView() || args[0]->IsString()); | 1663 | 1675 | CHECK(args[1]->IsUint32()); | 1664 | 1676 | uint32_t value = args[1].As<v8::Uint32>()->Value(); | | 1677 | + args.GetReturnValue().Set(CRC32Impl(args.GetIsolate(), args[0], value)); | | 1678 | +} | 1665 | 1679 |
| 1666 | | - uint32_t result = CallOnSequence<uint32_t>( | 1667 | | - args.GetIsolate(), | 1668 | | - args[0], | 1669 | | - [&](const char* data, size_t size) -> uint32_t { | 1670 | | - return crc32(value, reinterpret_cast<const Bytef*>(data), size); | 1671 | | - }); | 1672 | | - | 1673 | | - args.GetReturnValue().Set(result); | | 1680 | +static uint32_t FastCRC32(v8::Local<v8::Value> receiver, | | 1681 | + v8::Local<v8::Value> data, | | 1682 | + uint32_t value, | | 1683 | + // NOLINTNEXTLINE(runtime/references) | | 1684 | + v8::FastApiCallbackOptions& options) { | | 1685 | + TRACK_V8_FAST_API_CALL("zlib.crc32"); | | 1686 | + v8::HandleScope handle_scope(options.isolate); | | 1687 | + return CRC32Impl(options.isolate, data, value); | 1674 | 1688 | } | 1675 | 1689 |
| | 1690 | +static CFunction fast_crc32_(CFunction::Make(FastCRC32)); | | 1691 | + | 1676 | 1692 | void Initialize(Local<Object> target, | 1677 | 1693 | Local<Value> unused, | 1678 | 1694 | Local<Context> context, | @@ -1685,7 +1701,7 @@ void Initialize(Local<Object> target, | 1685 | 1701 | MakeClass<ZstdCompressStream>::Make(env, target, "ZstdCompress"); | 1686 | 1702 | MakeClass<ZstdDecompressStream>::Make(env, target, "ZstdDecompress"); | 1687 | 1703 |
| 1688 | | - SetMethod(context, target, "crc32", CRC32); | | 1704 | + SetFastMethodNoSideEffect(context, target, "crc32", CRC32, &fast_crc32_); | 1689 | 1705 | target->Set(env->context(), | 1690 | 1706 | FIXED_ONE_BYTE_STRING(env->isolate(), "ZLIB_VERSION"), | 1691 | 1707 | FIXED_ONE_BYTE_STRING(env->isolate(), ZLIB_VERSION)).Check(); | @@ -1698,6 +1714,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { | 1698 | 1714 | MakeClass<ZstdCompressStream>::Make(registry); | 1699 | 1715 | MakeClass<ZstdDecompressStream>::Make(registry); | 1700 | 1716 | registry->Register(CRC32); | | 1717 | + registry->Register(fast_crc32_); | 1701 | 1718 | } | 1702 | 1719 |
| 1703 | 1720 | } // anonymous namespace | |
Collapse file Copy file name to clipboard +26Lines changed: 26 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +// Flags: --expose-internals --no-warnings --allow-natives-syntax | | 2 | +'use strict'; | | 3 | + | | 4 | +const common = require('../common'); | | 5 | +const assert = require('assert'); | | 6 | +const zlib = require('zlib'); | | 7 | + | | 8 | +{ | | 9 | + function testFastPath() { | | 10 | + const expected = 0xd87f7e0c; // zlib.crc32('test', 0) | | 11 | + assert.strictEqual(zlib.crc32('test', 0), expected); | | 12 | + return expected; | | 13 | + } | | 14 | + | | 15 | + eval('%PrepareFunctionForOptimization(zlib.crc32)'); | | 16 | + testFastPath(); | | 17 | + eval('%OptimizeFunctionOnNextCall(zlib.crc32)'); | | 18 | + testFastPath(); | | 19 | + testFastPath(); | | 20 | + | | 21 | + if (common.isDebug) { | | 22 | + const { internalBinding } = require('internal/test/binding'); | | 23 | + const { getV8FastApiCallCount } = internalBinding('debug'); | | 24 | + assert.strictEqual(getV8FastApiCallCount('zlib.crc32'), 2); | | 25 | + } | | 26 | +} |
|