| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 4c9f3ed commit 02a51a7
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,7 +6,7 @@ const common = require('../common.js'); | |||
| 6 | 6 | const { Readable, Writable, pipeline } = require('stream'); | |
| 7 | 7 | ||
| 8 | 8 | const bench = common.createBenchmark(main, { | |
| 9 | - api: ['classic', 'webstream', 'iter', 'iter-sync'], | ||
| 9 | + api: ['classic', 'webstream', 'iter', 'iter-sync-source', 'iter-sync'], | ||
| 10 | 10 | datasize: [1024 * 1024, 16 * 1024 * 1024, 64 * 1024 * 1024], | |
| 11 | 11 | n: [5], | |
| 12 | 12 | }, { | |
@@ -26,6 +26,8 @@ function main({ api, datasize, n }) { | |||
| 26 | 26 | return benchWebStream(chunk, datasize, n, totalOps); | |
| 27 | 27 | case 'iter': | |
| 28 | 28 | return benchIter(chunk, datasize, n, totalOps); | |
| 29 | + case 'iter-sync-source': | ||
| 30 | + return benchIterSyncSource(chunk, datasize, n, totalOps); | ||
| 29 | 31 | case 'iter-sync': | |
| 30 | 32 | return benchIterSync(chunk, datasize, n, totalOps); | |
| 31 | 33 | } | |
@@ -101,6 +103,29 @@ function benchIter(chunk, datasize, n, totalOps) { | |||
| 101 | 103 | })(); | |
| 102 | 104 | } | |
| 103 | 105 | ||
| 106 | + function benchIterSyncSource(chunk, datasize, n, totalOps) { | ||
| 107 | + const { pipeTo } = require('stream/iter'); | ||
| 108 | + | ||
| 109 | + async function run() { | ||
| 110 | + let remaining = datasize; | ||
| 111 | + function* source() { | ||
| 112 | + while (remaining > 0) { | ||
| 113 | + const size = Math.min(remaining, chunk.length); | ||
| 114 | + remaining -= size; | ||
| 115 | + yield size === chunk.length ? chunk : chunk.subarray(0, size); | ||
| 116 | + } | ||
| 117 | + } | ||
| 118 | + const writer = { write() {}, writeSync() { return true; } }; | ||
| 119 | + await pipeTo(source(), writer); | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + (async () => { | ||
| 123 | + bench.start(); | ||
| 124 | + for (let i = 0; i < n; i++) await run(); | ||
| 125 | + bench.end(totalOps); | ||
| 126 | + })(); | ||
| 127 | + } | ||
| 128 | + | ||
| 104 | 129 | function benchIterSync(chunk, datasize, n, totalOps) { | |
| 105 | 130 | const { pipeToSync } = require('stream/iter'); | |
| 106 | 131 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,6 +8,8 @@ | |||
| 8 | 8 | ||
| 9 | 9 | const { | |
| 10 | 10 | ArrayBufferIsView, | |
| 11 | + ArrayFromAsync, | ||
| 12 | + ArrayIsArray, | ||
| 11 | 13 | ArrayPrototypePush, | |
| 12 | 14 | ArrayPrototypeSlice, | |
| 13 | 15 | PromisePrototypeThen, | |
@@ -38,7 +40,9 @@ const { | |||
| 38 | 40 | fromSync, | |
| 39 | 41 | isSyncIterable, | |
| 40 | 42 | isAsyncIterable, | |
| 43 | + isPrimitiveChunk, | ||
| 41 | 44 | isUint8ArrayBatch, | |
| 45 | + normalizeAsyncValue, | ||
| 42 | 46 | } = require('internal/streams/iter/from'); | |
| 43 | 47 | ||
| 44 | 48 | const { | |
@@ -53,7 +57,10 @@ const { | |||
| 53 | 57 | const { | |
| 54 | 58 | drainableProtocol, | |
| 55 | 59 | kSyncWriteAcceptedOnFalse, | |
| 60 | + kValidatedSource, | ||
| 56 | 61 | kValidatedTransform, | |
| 62 | + toAsyncStreamable, | ||
| 63 | + toStreamable, | ||
| 57 | 64 | } = require('internal/streams/iter/types'); | |
| 58 | 65 | ||
| 59 | 66 | // ============================================================================= | |
@@ -116,6 +123,22 @@ function parsePipeToArgs(args, requiredMethod) { | |||
| 116 | 123 | }; | |
| 117 | 124 | } | |
| 118 | 125 | ||
| 126 | + function canUseSyncIterablePipeToFastPath(source, transforms, signal) { | ||
| 127 | + if (signal !== undefined || | ||
| 128 | + transforms.length !== 0 || | ||
| 129 | + isPrimitiveChunk(source) || | ||
| 130 | + ArrayIsArray(source) || | ||
| 131 | + source?.[kValidatedSource] || | ||
| 132 | + !isSyncIterable(source) || | ||
| 133 | + isAsyncIterable(source)) { | ||
| 134 | + return false; | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + // Preserve from()'s top-level protocol precedence for custom iterables. | ||
| 138 | + return typeof source[toAsyncStreamable] !== 'function' && | ||
| 139 | + typeof source[toStreamable] !== 'function'; | ||
| 140 | + } | ||
| 141 | + | ||
| 119 | 142 | // ============================================================================= | |
| 120 | 143 | // Transform Output Flattening | |
| 121 | 144 | // ============================================================================= | |
@@ -822,12 +845,13 @@ async function pipeTo(source, ...args) { | |||
| 822 | 845 | // Check for abort | |
| 823 | 846 | signal?.throwIfAborted(); | |
| 824 | 847 | ||
| 825 | - // Normalize source via from() | ||
| 826 | - const normalized = from(source); | ||
| 848 | + const hasWriteSync = typeof writer.writeSync === 'function'; | ||
| 849 | + const useSyncIterableFastPath = | ||
| 850 | + hasWriteSync && canUseSyncIterablePipeToFastPath(source, transforms, signal); | ||
| 851 | + const normalized = useSyncIterableFastPath ? undefined : from(source); | ||
| 827 | 852 | ||
| 828 | 853 | let totalBytes = 0; | |
| 829 | 854 | const hasWritev = typeof writer.writev === 'function'; | |
| 830 | - const hasWriteSync = typeof writer.writeSync === 'function'; | ||
| 831 | 855 | const hasWritevSync = typeof writer.writevSync === 'function'; | |
| 832 | 856 | const hasEndSync = typeof writer.endSync === 'function'; | |
| 833 | 857 | const syncFalseCanBeAccepted = writer[kSyncWriteAcceptedOnFalse] === true; | |
@@ -908,8 +932,32 @@ async function pipeTo(source, ...args) { | |||
| 908 | 932 | } | |
| 909 | 933 | ||
| 910 | 934 | try { | |
| 911 | - // Fast path: no transforms - iterate normalized source directly | ||
| 912 | - if (transforms.length === 0) { | ||
| 935 | + if (useSyncIterableFastPath) { | ||
| 936 | + // Avoid from()'s async sync-iterable batching path. This keeps writes | ||
| 937 | + // incremental for synchronous sources while preserving async | ||
| 938 | + // normalization for non-primitive yielded values. | ||
| 939 | + for (const value of source) { | ||
| 940 | + if (isUint8ArrayBatch(value)) { | ||
| 941 | + if (value.length > 0) { | ||
| 942 | + const p = writeBatch(value); | ||
| 943 | + if (p) await p; | ||
| 944 | + } | ||
| 945 | + continue; | ||
| 946 | + } | ||
| 947 | + if (isUint8Array(value)) { | ||
| 948 | + const p = writeBatch([value]); | ||
| 949 | + if (p) await p; | ||
| 950 | + continue; | ||
| 951 | + } | ||
| 952 | + | ||
| 953 | + const batch = await ArrayFromAsync(normalizeAsyncValue(value)); | ||
| 954 | + if (batch.length > 0) { | ||
| 955 | + const p = writeBatch(batch); | ||
| 956 | + if (p) await p; | ||
| 957 | + } | ||
| 958 | + } | ||
| 959 | + } else if (transforms.length === 0) { | ||
| 960 | + // Fast path: no transforms - iterate normalized source directly | ||
| 913 | 961 | if (signal) { | |
| 914 | 962 | for await (const batch of normalized) { | |
| 915 | 963 | signal.throwIfAborted(); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -219,6 +219,79 @@ async function testPipeToSyncMinimalWriter() { | |||
| 219 | 219 | assert.strictEqual(chunks.length > 0, true); | |
| 220 | 220 | } | |
| 221 | 221 | ||
| 222 | + async function testPipeToSyncIterableFastPathWritesIncrementally() { | ||
| 223 | + let pulled = 0; | ||
| 224 | + let firstWritePulled = 0; | ||
| 225 | + const chunks = []; | ||
| 226 | + function* source() { | ||
| 227 | + for (let i = 0; i < 3; i++) { | ||
| 228 | + pulled++; | ||
| 229 | + yield new Uint8Array([0x61 + i]); | ||
| 230 | + } | ||
| 231 | + } | ||
| 232 | + const writer = { | ||
| 233 | + write: common.mustNotCall(), | ||
| 234 | + writeSync(chunk) { | ||
| 235 | + if (firstWritePulled === 0) { | ||
| 236 | + firstWritePulled = pulled; | ||
| 237 | + } | ||
| 238 | + chunks.push(chunk); | ||
| 239 | + return true; | ||
| 240 | + }, | ||
| 241 | + }; | ||
| 242 | + | ||
| 243 | + const totalBytes = await pipeTo(source(), writer); | ||
| 244 | + assert.strictEqual(totalBytes, 3); | ||
| 245 | + assert.strictEqual(firstWritePulled, 1); | ||
| 246 | + assert.deepStrictEqual(chunks, [ | ||
| 247 | + new Uint8Array([0x61]), | ||
| 248 | + new Uint8Array([0x62]), | ||
| 249 | + new Uint8Array([0x63]), | ||
| 250 | + ]); | ||
| 251 | + } | ||
| 252 | + | ||
| 253 | + async function testPipeToSyncIterableFastPathWriteFallback() { | ||
| 254 | + const asyncWrites = []; | ||
| 255 | + const writer = { | ||
| 256 | + writeSync(chunk) { | ||
| 257 | + return chunk[0] !== 0x62; | ||
| 258 | + }, | ||
| 259 | + async write(chunk) { | ||
| 260 | + asyncWrites.push(chunk); | ||
| 261 | + }, | ||
| 262 | + }; | ||
| 263 | + function* source() { | ||
| 264 | + yield new Uint8Array([0x61]); | ||
| 265 | + yield new Uint8Array([0x62]); | ||
| 266 | + yield new Uint8Array([0x63]); | ||
| 267 | + } | ||
| 268 | + | ||
| 269 | + const totalBytes = await pipeTo(source(), writer); | ||
| 270 | + assert.strictEqual(totalBytes, 3); | ||
| 271 | + assert.deepStrictEqual(asyncWrites, [new Uint8Array([0x62])]); | ||
| 272 | + } | ||
| 273 | + | ||
| 274 | + async function testPipeToSyncIterableFastPathAsyncValue() { | ||
| 275 | + const chunks = []; | ||
| 276 | + const writer = { | ||
| 277 | + write: common.mustNotCall(), | ||
| 278 | + writeSync(chunk) { | ||
| 279 | + chunks.push(chunk); | ||
| 280 | + return true; | ||
| 281 | + }, | ||
| 282 | + }; | ||
| 283 | + function* source() { | ||
| 284 | + yield Promise.resolve('a'); | ||
| 285 | + yield new Uint8Array([0x62]); | ||
| 286 | + } | ||
| 287 | + | ||
| 288 | + const totalBytes = await pipeTo(source(), writer); | ||
| 289 | + assert.strictEqual(totalBytes, 2); | ||
| 290 | + const result = new TextDecoder().decode( | ||
| 291 | + new Uint8Array(chunks.reduce((acc, c) => [...acc, ...c], []))); | ||
| 292 | + assert.strictEqual(result, 'ab'); | ||
| 293 | + } | ||
| 294 | + | ||
| 222 | 295 | Promise.all([ | |
| 223 | 296 | testPipeToSync(), | |
| 224 | 297 | testPipeTo(), | |
@@ -234,4 +307,7 @@ Promise.all([ | |||
| 234 | 307 | testPipeToSyncPreventClose(), | |
| 235 | 308 | testPipeToMinimalWriter(), | |
| 236 | 309 | testPipeToSyncMinimalWriter(), | |
| 310 | + testPipeToSyncIterableFastPathWritesIncrementally(), | ||
| 311 | + testPipeToSyncIterableFastPathWriteFallback(), | ||
| 312 | + testPipeToSyncIterableFastPathAsyncValue(), | ||
| 237 | 313 | ]).then(common.mustCall()); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments