From a5a60e68232ae26615ff2c297bea49314354c42c Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Thu, 15 Aug 2024 03:01:05 +0200 Subject: [PATCH] buffer: use fast API for writing one-byte strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/54310 PR-URL: https://github.com/nodejs/node/pull/54311 Reviewed-By: Michaƫl Zasso Reviewed-By: Yagiz Nizipli Reviewed-By: Santiago Gimeno Reviewed-By: James M Snell Reviewed-By: Matteo Collina Reviewed-By: Benjamin Gruenbaum --- .../buffers/buffer-write-string-short.js | 20 ++++++ lib/internal/buffer.js | 36 ++++++++-- src/node_buffer.cc | 70 ++++++++++++++++++- src/node_external_reference.h | 8 +++ 4 files changed, 125 insertions(+), 9 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..c547c1f993fb55 100644 --- a/lib/internal/buffer.js +++ b/lib/internal/buffer.js @@ -23,13 +23,13 @@ const { hexSlice, ucs2Slice, utf8Slice, - asciiWrite, + asciiWriteStatic, base64Write, base64urlWrite, - latin1Write, + latin1WriteStatic, hexWrite, ucs2Write, - utf8Write, + utf8WriteStatic, getZeroFillToggle, } = internalBinding('buffer'); @@ -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 diff --git a/src/node_buffer.cc b/src/node_buffer.cc index ad700b6fbc027e..0e1881eeb5f16d 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -1442,6 +1442,52 @@ 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()); + CHECK(dst.length() - offset <= std::numeric_limits::max()); + + max_length = std::min(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, @@ -1502,13 +1548,26 @@ void Initialize(Local target, SetMethodNoSideEffect(context, target, "ucs2Slice", StringSlice); SetMethodNoSideEffect(context, target, "utf8Slice", StringSlice); - SetMethod(context, target, "asciiWrite", StringWrite); SetMethod(context, target, "base64Write", StringWrite); SetMethod(context, target, "base64urlWrite", StringWrite); - SetMethod(context, target, "latin1Write", StringWrite); SetMethod(context, target, "hexWrite", StringWrite); 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); } @@ -1550,6 +1609,11 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(StringSlice); registry->Register(StringSlice); + registry->Register(SlowWriteString); + registry->Register(SlowWriteString); + 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 626c3da5b1b866..f09c73ddd6d34c 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) \