Skip to content

Commit 378e8ed

Browse files
committed
buffer: use fast API for writing one-byte strings
PR-URL: nodejs#54310
1 parent b00102e commit 378e8ed

File tree

4 files changed

+125
-9
lines changed

4 files changed

+125
-9
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: [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/internal/buffer.js

+30-6
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ const {
2323
hexSlice,
2424
ucs2Slice,
2525
utf8Slice,
26-
asciiWrite,
26+
asciiWriteStatic,
2727
base64Write,
2828
base64urlWrite,
29-
latin1Write,
29+
latin1WriteStatic,
3030
hexWrite,
3131
ucs2Write,
32-
utf8Write,
32+
utf8WriteStatic,
3333
getZeroFillToggle,
3434
} = internalBinding('buffer');
3535

@@ -1036,13 +1036,37 @@ function addBufferPrototypeMethods(proto) {
10361036
proto.hexSlice = hexSlice;
10371037
proto.ucs2Slice = ucs2Slice;
10381038
proto.utf8Slice = utf8Slice;
1039-
proto.asciiWrite = asciiWrite;
1039+
proto.asciiWrite = function(string, offset = 0, length = this.byteLength) {
1040+
if (offset < 0 || offset > this.byteLength) {
1041+
throw new ERR_BUFFER_OUT_OF_BOUNDS('offset');
1042+
}
1043+
if (length < 0 || length > this.byteLength - offset) {
1044+
throw new ERR_BUFFER_OUT_OF_BOUNDS('length');
1045+
}
1046+
return asciiWriteStatic(this, string, offset, length);
1047+
};
10401048
proto.base64Write = base64Write;
10411049
proto.base64urlWrite = base64urlWrite;
1042-
proto.latin1Write = latin1Write;
1050+
proto.latin1Write = function(string, offset = 0, length = this.byteLength) {
1051+
if (offset < 0 || offset > this.byteLength) {
1052+
throw new ERR_BUFFER_OUT_OF_BOUNDS('offset');
1053+
}
1054+
if (length < 0 || length > this.byteLength - offset) {
1055+
throw new ERR_BUFFER_OUT_OF_BOUNDS('length');
1056+
}
1057+
return latin1WriteStatic(this, string, offset, length);
1058+
};
10431059
proto.hexWrite = hexWrite;
10441060
proto.ucs2Write = ucs2Write;
1045-
proto.utf8Write = utf8Write;
1061+
proto.utf8Write = function(string, offset = 0, length = this.byteLength) {
1062+
if (offset < 0 || offset > this.byteLength) {
1063+
throw new ERR_BUFFER_OUT_OF_BOUNDS('offset');
1064+
}
1065+
if (length < 0 || length > this.byteLength - offset) {
1066+
throw new ERR_BUFFER_OUT_OF_BOUNDS('length');
1067+
}
1068+
return utf8WriteStatic(this, string, offset, length);
1069+
};
10461070
}
10471071

10481072
// This would better be placed in internal/worker/io.js, but that doesn't work

src/node_buffer.cc

+67-3
Original file line numberDiff line numberDiff line change
@@ -1425,6 +1425,52 @@ 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+
THROW_AND_RETURN_IF_OOB(
1444+
ParseArrayIndex(env, args[3], ts_obj_length - offset, &max_length));
1445+
1446+
max_length = std::min(ts_obj_length - offset, max_length);
1447+
1448+
if (max_length == 0) return args.GetReturnValue().Set(0);
1449+
1450+
uint32_t written = StringBytes::Write(
1451+
env->isolate(), ts_obj_data + offset, max_length, str, encoding);
1452+
args.GetReturnValue().Set(written);
1453+
}
1454+
1455+
uint32_t FastWriteString(Local<Value> receiver,
1456+
const v8::FastApiTypedArray<uint8_t>& dst,
1457+
const v8::FastOneByteString& src,
1458+
uint32_t offset,
1459+
uint32_t max_length) {
1460+
uint8_t* dst_data;
1461+
CHECK(dst.getStorageIfAligned(&dst_data));
1462+
CHECK(offset <= dst.length());
1463+
CHECK(dst.length() - offset <= std::numeric_limits<uint32_t>::max());
1464+
1465+
max_length = std::min<uint32_t>(dst.length() - offset, max_length);
1466+
1467+
memcpy(dst_data, src.data, max_length);
1468+
1469+
return max_length;
1470+
}
1471+
1472+
static v8::CFunction fast_write_string(v8::CFunction::Make(FastWriteString));
1473+
14281474
void Initialize(Local<Object> target,
14291475
Local<Value> unused,
14301476
Local<Context> context,
@@ -1486,13 +1532,26 @@ void Initialize(Local<Object> target,
14861532
SetMethodNoSideEffect(context, target, "ucs2Slice", StringSlice<UCS2>);
14871533
SetMethodNoSideEffect(context, target, "utf8Slice", StringSlice<UTF8>);
14881534

1489-
SetMethod(context, target, "asciiWrite", StringWrite<ASCII>);
14901535
SetMethod(context, target, "base64Write", StringWrite<BASE64>);
14911536
SetMethod(context, target, "base64urlWrite", StringWrite<BASE64URL>);
1492-
SetMethod(context, target, "latin1Write", StringWrite<LATIN1>);
14931537
SetMethod(context, target, "hexWrite", StringWrite<HEX>);
14941538
SetMethod(context, target, "ucs2Write", StringWrite<UCS2>);
1495-
SetMethod(context, target, "utf8Write", StringWrite<UTF8>);
1539+
1540+
SetFastMethod(context,
1541+
target,
1542+
"asciiWriteStatic",
1543+
SlowWriteString<ASCII>,
1544+
&fast_write_string);
1545+
SetFastMethod(context,
1546+
target,
1547+
"latin1WriteStatic",
1548+
SlowWriteString<LATIN1>,
1549+
&fast_write_string);
1550+
SetFastMethod(context,
1551+
target,
1552+
"utf8WriteStatic",
1553+
SlowWriteString<UTF8>,
1554+
&fast_write_string);
14961555

14971556
SetMethod(context, target, "getZeroFillToggle", GetZeroFillToggle);
14981557
}
@@ -1535,6 +1594,11 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
15351594
registry->Register(StringSlice<UCS2>);
15361595
registry->Register(StringSlice<UTF8>);
15371596

1597+
registry->Register(SlowWriteString<ASCII>);
1598+
registry->Register(SlowWriteString<LATIN1>);
1599+
registry->Register(SlowWriteString<UTF8>);
1600+
registry->Register(fast_write_string.GetTypeInfo());
1601+
registry->Register(FastWriteString);
15381602
registry->Register(StringWrite<ASCII>);
15391603
registry->Register(StringWrite<BASE64>);
15401604
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)