Skip to content

Commit f3259aa

Browse files
committed
buffer: optimize writing short strings
PR-URL: nodejs#54310
1 parent 298ff4f commit f3259aa

File tree

4 files changed

+105
-3
lines changed

4 files changed

+105
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const bench = common.createBenchmark(main, {
5+
encoding: [
6+
'', 'utf8', 'ascii', 'latin1',
7+
],
8+
len: [0, 1, 8, 16, 32],
9+
n: [1e6],
10+
});
11+
12+
function main({ len, n, encoding }) {
13+
const buf = Buffer.allocUnsafe(len);
14+
const string = Buffer.from('a'.repeat(len)).toString()
15+
bench.start();
16+
for (let i = 0; i < n; ++i) {
17+
buf.write(string, 0, encoding);
18+
}
19+
bench.end(n);
20+
}

lib/buffer.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ const {
7474
atob: _atob,
7575
btoa: _btoa,
7676
} = internalBinding('buffer');
77+
const bufferBinding = internalBinding('buffer');
78+
7779
const {
7880
constants: {
7981
ALL_PROPERTIES,
@@ -620,7 +622,7 @@ const encodingOps = {
620622
encoding: 'utf8',
621623
encodingVal: encodingsMap.utf8,
622624
byteLength: byteLengthUtf8,
623-
write: (buf, string, offset, len) => buf.utf8Write(string, offset, len),
625+
write: (buf, string, offset, len) => bufferBinding.utf8WriteStatic(buf, string, offset, len),
624626
slice: (buf, start, end) => buf.utf8Slice(start, end),
625627
indexOf: (buf, val, byteOffset, dir) =>
626628
indexOfString(buf, val, byteOffset, encodingsMap.utf8, dir),
@@ -647,7 +649,7 @@ const encodingOps = {
647649
encoding: 'latin1',
648650
encodingVal: encodingsMap.latin1,
649651
byteLength: (string) => string.length,
650-
write: (buf, string, offset, len) => buf.latin1Write(string, offset, len),
652+
write: (buf, string, offset, len) => bufferBinding.latin1WriteStatic(buf, string, offset, len),
651653
slice: (buf, start, end) => buf.latin1Slice(start, end),
652654
indexOf: (buf, val, byteOffset, dir) =>
653655
indexOfString(buf, val, byteOffset, encodingsMap.latin1, dir),
@@ -656,7 +658,7 @@ const encodingOps = {
656658
encoding: 'ascii',
657659
encodingVal: encodingsMap.ascii,
658660
byteLength: (string) => string.length,
659-
write: (buf, string, offset, len) => buf.asciiWrite(string, offset, len),
661+
write: (buf, string, offset, len) => bufferBinding.asciiWriteStatic(buf, string, offset, len),
660662
slice: (buf, start, end) => buf.asciiSlice(start, end),
661663
indexOf: (buf, val, byteOffset, dir) =>
662664
indexOfBuffer(buf,

src/node_buffer.cc

+72
Original file line numberDiff line numberDiff line change
@@ -1425,6 +1425,59 @@ void CopyArrayBuffer(const FunctionCallbackInfo<Value>& args) {
14251425
memcpy(dest, src, bytes_to_copy);
14261426
}
14271427

1428+
template <encoding encoding>
1429+
void SlowWriteString(const FunctionCallbackInfo<Value>& args) {
1430+
Environment* env = Environment::GetCurrent(args);
1431+
1432+
THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]);
1433+
SPREAD_BUFFER_ARG(args[0], ts_obj);
1434+
1435+
THROW_AND_RETURN_IF_NOT_STRING(env, args[1], "argument");
1436+
1437+
Local<String> str = args[1]->ToString(env->context()).ToLocalChecked();
1438+
1439+
size_t offset = 0;
1440+
size_t max_length = 0;
1441+
1442+
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(env, args[2], 0, &offset));
1443+
if (offset > ts_obj_length) {
1444+
return node::THROW_ERR_BUFFER_OUT_OF_BOUNDS(
1445+
env, "\"offset\" is outside of buffer bounds");
1446+
}
1447+
1448+
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(env, args[3], ts_obj_length - offset,
1449+
&max_length));
1450+
1451+
max_length = std::min(ts_obj_length - offset, max_length);
1452+
1453+
if (max_length == 0)
1454+
return args.GetReturnValue().Set(0);
1455+
1456+
uint32_t written = StringBytes::Write(
1457+
env->isolate(), ts_obj_data + offset, max_length, str, encoding);
1458+
args.GetReturnValue().Set(written);
1459+
}
1460+
1461+
uint32_t FastWriteString(Local<Value> receiver,
1462+
const v8::FastApiTypedArray<uint8_t>& dst,
1463+
const v8::FastOneByteString& src,
1464+
uint32_t offset,
1465+
uint32_t max_length) {
1466+
uint8_t* dst_data;
1467+
CHECK(dst.getStorageIfAligned(&dst_data));
1468+
1469+
if (offset > dst.length()) {
1470+
// TODO: Throw "\"offset\" is outside of buffer bound
1471+
}
1472+
1473+
memcpy(dst_data, src.data, max_length);
1474+
1475+
return max_length;
1476+
}
1477+
1478+
static v8::CFunction fast_write_string(
1479+
v8::CFunction::Make(FastWriteString));
1480+
14281481
void Initialize(Local<Object> target,
14291482
Local<Value> unused,
14301483
Local<Context> context,
@@ -1494,6 +1547,22 @@ void Initialize(Local<Object> target,
14941547
SetMethod(context, target, "ucs2Write", StringWrite<UCS2>);
14951548
SetMethod(context, target, "utf8Write", StringWrite<UTF8>);
14961549

1550+
SetFastMethod(context,
1551+
target,
1552+
"asciiWriteStatic",
1553+
SlowWriteString<ASCII>,
1554+
&fast_write_string);
1555+
SetFastMethod(context,
1556+
target,
1557+
"latin1WriteStatic",
1558+
SlowWriteString<LATIN1>,
1559+
&fast_write_string);
1560+
SetFastMethod(context,
1561+
target,
1562+
"utf8WriteStatic",
1563+
SlowWriteString<UTF8>,
1564+
&fast_write_string);
1565+
14971566
SetMethod(context, target, "getZeroFillToggle", GetZeroFillToggle);
14981567
}
14991568

@@ -1535,6 +1604,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
15351604
registry->Register(StringSlice<UCS2>);
15361605
registry->Register(StringSlice<UTF8>);
15371606

1607+
registry->Register(SlowWriteString<ASCII>);
1608+
registry->Register(fast_write_string.GetTypeInfo());
1609+
registry->Register(FastWriteString);
15381610
registry->Register(StringWrite<ASCII>);
15391611
registry->Register(StringWrite<BASE64>);
15401612
registry->Register(StringWrite<BASE64URL>);

src/node_external_reference.h

+8
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ using CFunctionWithInt64Fallback = void (*)(v8::Local<v8::Value>,
5656
v8::FastApiCallbackOptions&);
5757
using CFunctionWithBool = void (*)(v8::Local<v8::Value>, bool);
5858

59+
using CFunctionWriteString =
60+
uint32_t (*)(v8::Local<v8::Value> receiver,
61+
const v8::FastApiTypedArray<uint8_t>& dst,
62+
const v8::FastOneByteString& src,
63+
uint32_t offset,
64+
uint32_t max_length);
65+
5966
using CFunctionBufferCopy =
6067
uint32_t (*)(v8::Local<v8::Value> receiver,
6168
const v8::FastApiTypedArray<uint8_t>& source,
@@ -88,6 +95,7 @@ class ExternalReferenceRegistry {
8895
V(CFunctionWithInt64Fallback) \
8996
V(CFunctionWithBool) \
9097
V(CFunctionBufferCopy) \
98+
V(CFunctionWriteString) \
9199
V(const v8::CFunctionInfo*) \
92100
V(v8::FunctionCallback) \
93101
V(v8::AccessorNameGetterCallback) \

0 commit comments

Comments
 (0)