Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

buffer: use v8 fast API calls for Buffer.byteLength with sequential one-byte strings #46616

Merged
merged 2 commits into from
Feb 26, 2023
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
22 changes: 22 additions & 0 deletions benchmark/buffers/buffer-bytelength-buffer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';
const common = require('../common');

const bench = common.createBenchmark(main, {
len: [2, 16, 256], // x16
n: [4e6],
});

function main({ n, len }) {
const data = Buffer.alloc(len * 16, 'a');
const expected = Buffer.byteLength(data, 'buffer');
let changed = false;
bench.start();
for (let i = 0; i < n; i++) {
const actual = Buffer.byteLength(data, 'buffer');
if (expected !== actual) { changed = true; }
}
bench.end(n);
if (changed) {
throw new Error('Result changed during iteration');
}
}
40 changes: 40 additions & 0 deletions benchmark/buffers/buffer-bytelength-string.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict';
const common = require('../common');

const bench = common.createBenchmark(main, {
type: ['one_byte', 'two_bytes', 'three_bytes', 'four_bytes'],
encoding: ['utf8', 'base64'],
repeat: [1, 2, 16, 256], // x16
n: [4e6],
});

// 16 chars each
const chars = {
one_byte: 'hello brendan!!!',
two_bytes: 'ΰαβγδεζηθικλμνξο',
three_bytes: '挰挱挲挳挴挵挶挷挸挹挺挻挼挽挾挿',
four_bytes: '𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎𠸏𠹷𠺝𠺢',
};

function getInput(type, repeat, encoding) {
const original = (repeat === 1) ? chars[type] : chars[type].repeat(repeat);
if (encoding === 'base64') {
Buffer.from(original, 'utf8').toString('base64');
}
return original;
}

function main({ n, repeat, encoding, type }) {
const data = getInput(type, repeat, encoding);
const expected = Buffer.byteLength(data, encoding);
let changed = false;
bench.start();
for (let i = 0; i < n; i++) {
const actual = Buffer.byteLength(data, encoding);
if (expected !== actual) { changed = true; }
}
bench.end(n);
if (changed) {
throw new Error('Result changed during iteration');
}
}
49 changes: 0 additions & 49 deletions benchmark/buffers/buffer-bytelength.js

This file was deleted.

28 changes: 25 additions & 3 deletions src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "string_bytes.h"
#include "string_search.h"
#include "util-inl.h"
#include "v8-fast-api-calls.h"
#include "v8.h"

#include <cstring>
Expand Down Expand Up @@ -786,14 +787,29 @@ void StringWrite(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(written);
}

void ByteLengthUtf8(const FunctionCallbackInfo<Value> &args) {
void SlowByteLengthUtf8(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsString());

// Fast case: avoid StringBytes on UTF8 string. Jump to v8.
args.GetReturnValue().Set(args[0].As<String>()->Utf8Length(env->isolate()));
}

uint32_t FastByteLengthUtf8(Local<Value> receiver,
const v8::FastOneByteString& source) {
uint32_t result = 0;
uint32_t length = source.length;
const uint8_t* data = reinterpret_cast<const uint8_t*>(source.data);
for (uint32_t i = 0; i < length; ++i) {
result += (data[i] >> 7);
}
ronag marked this conversation as resolved.
Show resolved Hide resolved
result += length;
return result;
}

static v8::CFunction fast_byte_length_utf8(
v8::CFunction::Make(FastByteLengthUtf8));

// Normalize val to be an integer in the range of [1, -1] since
// implementations of memcmp() can vary by platform.
static int normalizeCompareVal(int val, size_t a_length, size_t b_length) {
Expand Down Expand Up @@ -1368,7 +1384,11 @@ void Initialize(Local<Object> target,
SetMethodNoSideEffect(context, target, "createFromString", CreateFromString);
SetMethodNoSideEffect(context, target, "decodeUTF8", DecodeUTF8);

SetMethodNoSideEffect(context, target, "byteLengthUtf8", ByteLengthUtf8);
SetFastMethodNoSideEffect(context,
target,
"byteLengthUtf8",
SlowByteLengthUtf8,
&fast_byte_length_utf8);
SetMethod(context, target, "copy", Copy);
SetMethodNoSideEffect(context, target, "compare", Compare);
SetMethodNoSideEffect(context, target, "compareOffset", CompareOffset);
Expand Down Expand Up @@ -1429,7 +1449,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(CreateFromString);
registry->Register(DecodeUTF8);

registry->Register(ByteLengthUtf8);
registry->Register(SlowByteLengthUtf8);
registry->Register(fast_byte_length_utf8.GetTypeInfo());
registry->Register(FastByteLengthUtf8);
registry->Register(Copy);
registry->Register(Compare);
registry->Register(CompareOffset);
Expand Down
3 changes: 3 additions & 0 deletions src/node_external_reference.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

namespace node {

using CFunctionCallbackWithOneByteString =
uint32_t (*)(v8::Local<v8::Value>, const v8::FastOneByteString&);
using CFunctionCallback = void (*)(v8::Local<v8::Value> receiver);

// This class manages the external references from the V8 heap
Expand All @@ -20,6 +22,7 @@ class ExternalReferenceRegistry {

#define ALLOWED_EXTERNAL_REFERENCE_TYPES(V) \
V(CFunctionCallback) \
V(CFunctionCallbackWithOneByteString) \
V(const v8::CFunctionInfo*) \
V(v8::FunctionCallback) \
V(v8::AccessorGetterCallback) \
Expand Down