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
PR-URL: nodejs#54311
Reviewed-By: Michaël Zasso <[email protected]>
Reviewed-By: Yagiz Nizipli <[email protected]>
Reviewed-By: Santiago Gimeno <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
  • Loading branch information
ronag authored Aug 15, 2024
1 parent 75d25bc commit ccf05ef
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 9 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: [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);
}
36 changes: 30 additions & 6 deletions lib/internal/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ const {
hexSlice,
ucs2Slice,
utf8Slice,
asciiWrite,
asciiWriteStatic,
base64Write,
base64urlWrite,
latin1Write,
latin1WriteStatic,
hexWrite,
ucs2Write,
utf8Write,
utf8WriteStatic,
getZeroFillToggle,
} = internalBinding('buffer');

Expand Down Expand Up @@ -1036,13 +1036,37 @@ 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 < 0 || offset > this.byteLength) {
throw new ERR_BUFFER_OUT_OF_BOUNDS('offset');
}
if (length < 0 || length > this.byteLength - offset) {
throw new ERR_BUFFER_OUT_OF_BOUNDS('length');
}
return 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 < 0 || offset > this.byteLength) {
throw new ERR_BUFFER_OUT_OF_BOUNDS('offset');
}
if (length < 0 || length > this.byteLength - offset) {
throw new ERR_BUFFER_OUT_OF_BOUNDS('length');
}
return 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 < 0 || offset > this.byteLength) {
throw new ERR_BUFFER_OUT_OF_BOUNDS('offset');
}
if (length < 0 || length > this.byteLength - offset) {
throw new ERR_BUFFER_OUT_OF_BOUNDS('length');
}
return utf8WriteStatic(this, string, offset, length);
};
}

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

max_length = std::min<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 @@ -1502,13 +1548,26 @@ void Initialize(Local<Object> target,
SetMethodNoSideEffect(context, target, "ucs2Slice", StringSlice<UCS2>);
SetMethodNoSideEffect(context, target, "utf8Slice", StringSlice<UTF8>);

SetMethod(context, target, "asciiWrite", StringWrite<ASCII>);
SetMethod(context, target, "base64Write", StringWrite<BASE64>);
SetMethod(context, target, "base64urlWrite", StringWrite<BASE64URL>);
SetMethod(context, target, "latin1Write", StringWrite<LATIN1>);
SetMethod(context, target, "hexWrite", StringWrite<HEX>);
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 @@ -1550,6 +1609,11 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(StringSlice<UCS2>);
registry->Register(StringSlice<UTF8>);

registry->Register(SlowWriteString<ASCII>);
registry->Register(SlowWriteString<LATIN1>);
registry->Register(SlowWriteString<UTF8>);
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 ccf05ef

Please sign in to comment.