mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
stream: avoid per-write allocations in stream/iter writers
Every write() and writeSync() allocated a `{ __proto__: null, context }`
options object for the WebIDL chunk conversion, and for a Uint8Array
chunk a second, six-property one with [AllowShared] and
[AllowResizable]. Both are created in V8 dictionary mode.
Return Uint8Array chunks directly from the WriterChunk converter: with
[AllowShared] and [AllowResizable], the Uint8Array conversion cannot
reject a value isUint8Array() accepts and returns the same object. Use
shared, frozen conversion contexts for chunks, chunk sequences and
write options; the converters only read them to build error messages.
Writing 1e6 16-byte Uint8Array chunks into a push() stream and reading
them back is about 27% faster with writeSync() and with writevSync()
(4 chunks per call). String chunks are unaffected. Behavior and error
messages are unchanged.
Assisted-by: OpenCode
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66648
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
This commit is contained in:
1 parent
778306c001
commit
724190cfdc
2 files changed
+16
-27
No files matched your search
@@ -412,6 +412,14 @@ function concatBytes(chunks) {
|
||||
return concatenated;
|
||||
}
|
||||
|
||||
// Conversion contexts for the per-write paths. The converters only read
|
||||
// them (to build error messages), so they are shared rather than allocated
|
||||
// on every write.
|
||||
const kChunkContext = ObjectFreeze({ __proto__: null, context: 'chunk' });
|
||||
const kChunksContext = ObjectFreeze({ __proto__: null, context: 'chunks' });
|
||||
const kWriteOptionsContext =
|
||||
ObjectFreeze({ __proto__: null, context: 'options' });
|
||||
|
||||
/**
|
||||
* Convert an array of chunks (strings or Uint8Arrays) to a Uint8Array[].
|
||||
* Always returns a fresh copy of the array.
|
||||
@@ -419,10 +427,7 @@ function concatBytes(chunks) {
|
||||
* @returns {Uint8Array[]}
|
||||
*/
|
||||
function convertChunks(chunks) {
|
||||
chunks = converters.WriterChunkSequence(chunks, {
|
||||
__proto__: null,
|
||||
context: 'chunks',
|
||||
});
|
||||
chunks = converters.WriterChunkSequence(chunks, kChunksContext);
|
||||
const len = chunks.length;
|
||||
const result = new Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
@@ -437,17 +442,11 @@ function convertChunks(chunks) {
|
||||
* @returns {AbortSignal|undefined}
|
||||
*/
|
||||
function getWriterSignal(options) {
|
||||
return converters.WriteOptions(options, {
|
||||
__proto__: null,
|
||||
context: 'options',
|
||||
}).signal;
|
||||
return converters.WriteOptions(options, kWriteOptionsContext).signal;
|
||||
}
|
||||
|
||||
function toWriterUint8Array(chunk) {
|
||||
return toUint8Array(converters.WriterChunk(chunk, {
|
||||
__proto__: null,
|
||||
context: 'chunk',
|
||||
}));
|
||||
return toUint8Array(converters.WriterChunk(chunk, kChunkContext));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,17 +27,6 @@ function enforceRangeUnsignedLongLong(value, options = { __proto__: null }) {
|
||||
});
|
||||
}
|
||||
|
||||
function allowStreamBufferOptions(options) {
|
||||
return {
|
||||
__proto__: null,
|
||||
prefix: options.prefix,
|
||||
context: options.context,
|
||||
code: options.code,
|
||||
allowShared: true,
|
||||
allowResizable: true,
|
||||
};
|
||||
}
|
||||
|
||||
converters.AbortSignal = baseConverters.AbortSignal;
|
||||
converters.BackpressurePolicy = createEnumConverter('BackpressurePolicy', [
|
||||
'strict',
|
||||
@@ -48,10 +37,11 @@ converters.BackpressurePolicy = createEnumConverter('BackpressurePolicy', [
|
||||
converters.unsignedLongLong = unsignedLongLong;
|
||||
converters.enforceRangeUnsignedLongLong = enforceRangeUnsignedLongLong;
|
||||
converters.WriterChunk = (value, options = { __proto__: null }) => {
|
||||
if (isUint8Array(value)) {
|
||||
return baseConverters.Uint8Array(
|
||||
value, allowStreamBufferOptions(options));
|
||||
}
|
||||
// The chunk is a Uint8Array with [AllowShared] and [AllowResizable]. The
|
||||
// Uint8Array conversion cannot reject a value isUint8Array() accepts when
|
||||
// both are allowed, and returns the same object, so skip it: writes are
|
||||
// hot, and the conversion would need its own options object.
|
||||
if (isUint8Array(value)) return value;
|
||||
return baseConverters.USVString(value, options);
|
||||
};
|
||||
converters.WriterChunkSequence = createSequenceConverter(
|
||||
|
||||
Reference in new issue
Block a user