Skip to content

Commit

Permalink
buffer: optimize writing short strings
Browse files Browse the repository at this point in the history
PR-URL: #54310
  • Loading branch information
ronag committed Aug 11, 2024
1 parent 298ff4f commit 9c98f8f
Show file tree
Hide file tree
Showing 4 changed files with 110 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()

Check failure on line 14 in benchmark/buffers/buffer-write-string-short.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Missing semicolon
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string, 0, encoding);
}
bench.end(n);
}
13 changes: 10 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,

Check failure on line 32 in lib/internal/buffer.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

'utf8Write' is assigned a value but never used
getZeroFillToggle,
} = internalBinding('buffer');
const bufferBinding = internalBinding('buffer');

const {
privateSymbols: {
Expand Down Expand Up @@ -1036,13 +1037,19 @@ function addBufferPrototypeMethods(proto) {
proto.hexSlice = hexSlice;
proto.ucs2Slice = ucs2Slice;
proto.utf8Slice = utf8Slice;
proto.asciiWrite = asciiWrite;
proto.asciiWrite = function (string, offset = 0, length = this.length) {

Check failure on line 1040 in lib/internal/buffer.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Unexpected space before function parentheses
return bufferBinding.asciiWriteStatic(this, string, offset, length);
}

Check failure on line 1042 in lib/internal/buffer.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Missing semicolon
proto.base64Write = base64Write;
proto.base64urlWrite = base64urlWrite;
proto.latin1Write = latin1Write;
proto.latin1Write = function (string, offset = 0, length = this.length) {

Check failure on line 1045 in lib/internal/buffer.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Unexpected space before function parentheses
return bufferBinding.latin1WriteStatic(this, string, offset, length);
}

Check failure on line 1047 in lib/internal/buffer.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Missing semicolon
proto.hexWrite = hexWrite;
proto.ucs2Write = ucs2Write;
proto.utf8Write = utf8Write;
proto.utf8Write = function (string, offset = 0, length = this.length) {

Check failure on line 1050 in lib/internal/buffer.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Unexpected space before function parentheses
return bufferBinding.utf8WriteStatic(this, string, offset, length);
}

Check failure on line 1052 in lib/internal/buffer.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Missing semicolon
}

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

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<Object> target,
Local<Value> unused,
Local<Context> context,
Expand Down Expand Up @@ -1494,6 +1547,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 +1604,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 9c98f8f

Please sign in to comment.