Skip to content

Commit

Permalink
buffer: optimize writing short strings
Browse files Browse the repository at this point in the history
PR-URL: #54310
  • Loading branch information
ronag committed Aug 11, 2024
1 parent 298ff4f commit f3259aa
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 3 deletions.
20 changes: 20 additions & 0 deletions benchmark/buffers/buffer-write-string-short.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';

const common = require('../common.js');
const bench = common.createBenchmark(main, {
encoding: [
'', 'utf8', 'ascii', 'latin1',
],
len: [0, 1, 8, 16, 32],
n: [1e6],
});

function main({ len, n, encoding }) {
const buf = Buffer.allocUnsafe(len);
const string = Buffer.from('a'.repeat(len)).toString()
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string, 0, encoding);
}
bench.end(n);
}
8 changes: 5 additions & 3 deletions lib/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ const {
atob: _atob,
btoa: _btoa,
} = internalBinding('buffer');
const bufferBinding = internalBinding('buffer');

const {
constants: {
ALL_PROPERTIES,
Expand Down Expand Up @@ -620,7 +622,7 @@ const encodingOps = {
encoding: 'utf8',
encodingVal: encodingsMap.utf8,
byteLength: byteLengthUtf8,
write: (buf, string, offset, len) => buf.utf8Write(string, offset, len),
write: (buf, string, offset, len) => bufferBinding.utf8WriteStatic(buf, string, offset, len),
slice: (buf, start, end) => buf.utf8Slice(start, end),
indexOf: (buf, val, byteOffset, dir) =>
indexOfString(buf, val, byteOffset, encodingsMap.utf8, dir),
Expand All @@ -647,7 +649,7 @@ const encodingOps = {
encoding: 'latin1',
encodingVal: encodingsMap.latin1,
byteLength: (string) => string.length,
write: (buf, string, offset, len) => buf.latin1Write(string, offset, len),
write: (buf, string, offset, len) => bufferBinding.latin1WriteStatic(buf, string, offset, len),
slice: (buf, start, end) => buf.latin1Slice(start, end),
indexOf: (buf, val, byteOffset, dir) =>
indexOfString(buf, val, byteOffset, encodingsMap.latin1, dir),
Expand All @@ -656,7 +658,7 @@ const encodingOps = {
encoding: 'ascii',
encodingVal: encodingsMap.ascii,
byteLength: (string) => string.length,
write: (buf, string, offset, len) => buf.asciiWrite(string, offset, len),
write: (buf, string, offset, len) => bufferBinding.asciiWriteStatic(buf, string, offset, len),
slice: (buf, start, end) => buf.asciiSlice(start, end),
indexOf: (buf, val, byteOffset, dir) =>
indexOfBuffer(buf,
Expand Down
72 changes: 72 additions & 0 deletions src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1425,6 +1425,59 @@ void CopyArrayBuffer(const FunctionCallbackInfo<Value>& args) {
memcpy(dest, src, bytes_to_copy);
}

template <encoding encoding>
void SlowWriteString(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]);
SPREAD_BUFFER_ARG(args[0], ts_obj);

THROW_AND_RETURN_IF_NOT_STRING(env, args[1], "argument");

Local<String> str = args[1]->ToString(env->context()).ToLocalChecked();

size_t offset = 0;
size_t max_length = 0;

THROW_AND_RETURN_IF_OOB(ParseArrayIndex(env, args[2], 0, &offset));
if (offset > ts_obj_length) {
return node::THROW_ERR_BUFFER_OUT_OF_BOUNDS(
env, "\"offset\" is outside of buffer bounds");
}

THROW_AND_RETURN_IF_OOB(ParseArrayIndex(env, args[3], ts_obj_length - offset,
&max_length));

max_length = std::min(ts_obj_length - offset, max_length);

if (max_length == 0)
return args.GetReturnValue().Set(0);

uint32_t written = StringBytes::Write(
env->isolate(), ts_obj_data + offset, max_length, str, encoding);
args.GetReturnValue().Set(written);
}

uint32_t FastWriteString(Local<Value> receiver,
const v8::FastApiTypedArray<uint8_t>& dst,
const v8::FastOneByteString& src,
uint32_t offset,
uint32_t max_length) {
uint8_t* dst_data;
CHECK(dst.getStorageIfAligned(&dst_data));

if (offset > dst.length()) {
// TODO: Throw "\"offset\" is outside of buffer bound
}

memcpy(dst_data, src.data, max_length);

return max_length;
}

static v8::CFunction fast_write_string(
v8::CFunction::Make(FastWriteString));

void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
Expand Down Expand Up @@ -1494,6 +1547,22 @@ void Initialize(Local<Object> target,
SetMethod(context, target, "ucs2Write", StringWrite<UCS2>);
SetMethod(context, target, "utf8Write", StringWrite<UTF8>);

SetFastMethod(context,
target,
"asciiWriteStatic",
SlowWriteString<ASCII>,
&fast_write_string);
SetFastMethod(context,
target,
"latin1WriteStatic",
SlowWriteString<LATIN1>,
&fast_write_string);
SetFastMethod(context,
target,
"utf8WriteStatic",
SlowWriteString<UTF8>,
&fast_write_string);

SetMethod(context, target, "getZeroFillToggle", GetZeroFillToggle);
}

Expand Down Expand Up @@ -1535,6 +1604,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(StringSlice<UCS2>);
registry->Register(StringSlice<UTF8>);

registry->Register(SlowWriteString<ASCII>);
registry->Register(fast_write_string.GetTypeInfo());
registry->Register(FastWriteString);
registry->Register(StringWrite<ASCII>);
registry->Register(StringWrite<BASE64>);
registry->Register(StringWrite<BASE64URL>);
Expand Down
8 changes: 8 additions & 0 deletions src/node_external_reference.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ using CFunctionWithInt64Fallback = void (*)(v8::Local<v8::Value>,
v8::FastApiCallbackOptions&);
using CFunctionWithBool = void (*)(v8::Local<v8::Value>, bool);

using CFunctionWriteString =
uint32_t (*)(v8::Local<v8::Value> receiver,
const v8::FastApiTypedArray<uint8_t>& dst,
const v8::FastOneByteString& src,
uint32_t offset,
uint32_t max_length);

using CFunctionBufferCopy =
uint32_t (*)(v8::Local<v8::Value> receiver,
const v8::FastApiTypedArray<uint8_t>& source,
Expand Down Expand Up @@ -88,6 +95,7 @@ class ExternalReferenceRegistry {
V(CFunctionWithInt64Fallback) \
V(CFunctionWithBool) \
V(CFunctionBufferCopy) \
V(CFunctionWriteString) \
V(const v8::CFunctionInfo*) \
V(v8::FunctionCallback) \
V(v8::AccessorNameGetterCallback) \
Expand Down

0 comments on commit f3259aa

Please sign in to comment.