diff --git a/benchmark/buffers/buffer-write-string-short.js b/benchmark/buffers/buffer-write-string-short.js new file mode 100644 index 00000000000000..152a6c1ede2abe --- /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: [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); +} diff --git a/lib/buffer.js b/lib/buffer.js index 4e6031afdb3919..0af1aca99cb4e4 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -74,6 +74,8 @@ const { atob: _atob, btoa: _btoa, } = internalBinding('buffer'); +const bufferBinding = internalBinding('buffer'); + const { constants: { ALL_PROPERTIES, @@ -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), @@ -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), @@ -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, diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 6e141b974131cc..0bb73a4168717f 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -1425,6 +1425,59 @@ 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)); + 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 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)); + + 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 target, Local unused, Local context, @@ -1494,6 +1547,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 +1604,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) \