Skip to content

Commit

Permalink
buffer: use fast API for writing one-byte strings
Browse files Browse the repository at this point in the history
PR-URL: nodejs#54310
  • Loading branch information
ronag committed Aug 11, 2024
1 parent 298ff4f commit 9cb44af
Show file tree
Hide file tree
Showing 4 changed files with 113 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);
}
22 changes: 19 additions & 3 deletions lib/internal/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const {
utf8Write,
getZeroFillToggle,
} = internalBinding('buffer');
const bufferBinding = internalBinding('buffer');

const {
privateSymbols: {
Expand Down Expand Up @@ -1036,13 +1037,28 @@ function addBufferPrototypeMethods(proto) {
proto.hexSlice = hexSlice;
proto.ucs2Slice = ucs2Slice;
proto.utf8Slice = utf8Slice;
proto.asciiWrite = asciiWrite;
proto.asciiWrite = function (string, offset = 0, length = this.byteLength) {
if (offset > this.byteLength) {
throw new ERR_BUFFER_OUT_OF_BOUNDS('offset');
}
return bufferBinding.asciiWriteStatic(this, string, offset, length);
}
proto.base64Write = base64Write;
proto.base64urlWrite = base64urlWrite;
proto.latin1Write = latin1Write;
proto.latin1Write = function (string, offset = 0, length = this.byteLength) {
if (offset > this.byteLength) {
throw new ERR_BUFFER_OUT_OF_BOUNDS('offset');
}
return bufferBinding.latin1WriteStatic(this, string, offset, length);
}
proto.hexWrite = hexWrite;
proto.ucs2Write = ucs2Write;
proto.utf8Write = utf8Write;
proto.utf8Write = function (string, offset = 0, length = this.byteLength) {
if (offset > this.byteLength) {
throw new ERR_BUFFER_OUT_OF_BOUNDS('offset');
}
return bufferBinding.utf8WriteStatic(this, string, offset, length);
}
}

// This would better be placed in internal/worker/io.js, but that doesn't work
Expand Down
66 changes: 66 additions & 0 deletions src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1425,6 +1425,53 @@ 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));
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));
CHECK(offset <= dst.length());

max_length = std::min(static_cast<uint32_t>(dst.length()) - offset, max_length);

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 +1541,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 +1598,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 9cb44af

Please sign in to comment.