From d47c4e402ce06f6e9358aa0ac3ea33c364152adb Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Sat, 10 Aug 2024 20:49:11 +0200 Subject: [PATCH] buffer: use fast API for writing one-byte strings PR-URL: https://github.com/nodejs/node/pull/54310 --- .../buffers/buffer-write-string-short.js | 20 ++++++ lib/internal/buffer.js | 22 ++++++- src/node_buffer.cc | 66 +++++++++++++++++++ src/node_external_reference.h | 8 +++ 4 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 benchmark/buffers/buffer-write-string-short.js diff --git a/benchmark/buffers/buffer-write-string-short.js b/benchmark/buffers/buffer-write-string-short.js new file mode 100644 index 00000000000000..8f02b70aabb56a --- /dev/null +++ b/benchmark/buffers/buffer-write-string-short.js @@ -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); +} diff --git a/lib/internal/buffer.js b/lib/internal/buffer.js index a65dd43263714e..e95397bd53981e 100644 --- a/lib/internal/buffer.js +++ b/lib/internal/buffer.js @@ -32,6 +32,7 @@ const { utf8Write, getZeroFillToggle, } = internalBinding('buffer'); +const bufferBinding = internalBinding('buffer'); const { privateSymbols: { @@ -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 diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 6e141b974131cc..7cfa8013fee529 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -1425,6 +1425,53 @@ void CopyArrayBuffer(const FunctionCallbackInfo& args) { memcpy(dest, src, bytes_to_copy); } +template +void SlowWriteString(const FunctionCallbackInfo& 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 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 receiver, + const v8::FastApiTypedArray& 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(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 target, Local unused, Local context, @@ -1494,6 +1541,22 @@ void Initialize(Local target, SetMethod(context, target, "ucs2Write", StringWrite); SetMethod(context, target, "utf8Write", StringWrite); + SetFastMethod(context, + target, + "asciiWriteStatic", + SlowWriteString, + &fast_write_string); + SetFastMethod(context, + target, + "latin1WriteStatic", + SlowWriteString, + &fast_write_string); + SetFastMethod(context, + target, + "utf8WriteStatic", + SlowWriteString, + &fast_write_string); + SetMethod(context, target, "getZeroFillToggle", GetZeroFillToggle); } @@ -1535,6 +1598,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(StringSlice); registry->Register(StringSlice); + registry->Register(SlowWriteString); + registry->Register(fast_write_string.GetTypeInfo()); + registry->Register(FastWriteString); registry->Register(StringWrite); registry->Register(StringWrite); registry->Register(StringWrite); diff --git a/src/node_external_reference.h b/src/node_external_reference.h index b59a3a9e9c957a..0f38b6aa659ba4 100644 --- a/src/node_external_reference.h +++ b/src/node_external_reference.h @@ -56,6 +56,13 @@ using CFunctionWithInt64Fallback = void (*)(v8::Local, v8::FastApiCallbackOptions&); using CFunctionWithBool = void (*)(v8::Local, bool); +using CFunctionWriteString = + uint32_t (*)(v8::Local receiver, + const v8::FastApiTypedArray& dst, + const v8::FastOneByteString& src, + uint32_t offset, + uint32_t max_length); + using CFunctionBufferCopy = uint32_t (*)(v8::Local receiver, const v8::FastApiTypedArray& source, @@ -88,6 +95,7 @@ class ExternalReferenceRegistry { V(CFunctionWithInt64Fallback) \ V(CFunctionWithBool) \ V(CFunctionBufferCopy) \ + V(CFunctionWriteString) \ V(const v8::CFunctionInfo*) \ V(v8::FunctionCallback) \ V(v8::AccessorNameGetterCallback) \