|
| 1 | +// Compare FileHandle.createReadStream() vs readableWebStream() vs pull() |
| 2 | +// reading a large file through two transforms: uppercase then compress. |
| 3 | +'use strict'; |
| 4 | + |
| 5 | +constcommon=require('../common.js'); |
| 6 | +constfs=require('fs'); |
| 7 | +constzlib=require('zlib'); |
| 8 | +const{ Transform, Writable, pipeline }=require('stream'); |
| 9 | + |
| 10 | +consttmpdir=require('../../test/common/tmpdir'); |
| 11 | +tmpdir.refresh(); |
| 12 | +constfilename=tmpdir.resolve(`.removeme-benchmark-garbage-${process.pid}`); |
| 13 | + |
| 14 | +constbench=common.createBenchmark(main,{ |
| 15 | +api: ['classic','webstream','pull'], |
| 16 | +compression: ['gzip','deflate','brotli','zstd'], |
| 17 | +filesize: [1024*1024,16*1024*1024,64*1024*1024], |
| 18 | +n: [5], |
| 19 | +},{ |
| 20 | +flags: ['--experimental-stream-iter'], |
| 21 | +// Classic and webstream only support gzip (native zlib / CompressionStream). |
| 22 | +// Brotli, deflate, zstd are pull-only via stream/iter transforms. |
| 23 | +combinationFilter({ api, compression }){ |
| 24 | +if(api==='classic'&&compression!=='gzip')returnfalse; |
| 25 | +if(api==='webstream'&&compression!=='gzip')returnfalse; |
| 26 | +returntrue; |
| 27 | +}, |
| 28 | +}); |
| 29 | + |
| 30 | +functionmain({ api, compression, filesize, n }){ |
| 31 | +// Create the fixture file with repeating lowercase ASCII |
| 32 | +constchunk=Buffer.alloc(Math.min(filesize,64*1024),'abcdefghij'); |
| 33 | +constfd=fs.openSync(filename,'w'); |
| 34 | +letremaining=filesize; |
| 35 | +while(remaining>0){ |
| 36 | +consttoWrite=Math.min(remaining,chunk.length); |
| 37 | +fs.writeSync(fd,chunk,0,toWrite); |
| 38 | +remaining-=toWrite; |
| 39 | +} |
| 40 | +fs.closeSync(fd); |
| 41 | + |
| 42 | +if(api==='classic'){ |
| 43 | +benchClassic(n,filesize).then(()=>cleanup()); |
| 44 | +}elseif(api==='webstream'){ |
| 45 | +benchWebStream(n,filesize).then(()=>cleanup()); |
| 46 | +}else{ |
| 47 | +benchPull(n,filesize,compression).then(()=>cleanup()); |
| 48 | +} |
| 49 | +} |
| 50 | + |
| 51 | +functioncleanup(){ |
| 52 | +try{fs.unlinkSync(filename);}catch{/* ignore */} |
| 53 | +} |
| 54 | + |
| 55 | +// Stateless uppercase transform (shared by all paths) |
| 56 | +functionuppercaseChunk(chunk){ |
| 57 | +constbuf=Buffer.allocUnsafe(chunk.length); |
| 58 | +for(leti=0;i<chunk.length;i++){ |
| 59 | +constb=chunk[i]; |
| 60 | +buf[i]=(b>=0x61&&b<=0x7a) ? b-0x20 : b; |
| 61 | +} |
| 62 | +returnbuf; |
| 63 | +} |
| 64 | + |
| 65 | +// --------------------------------------------------------------------------- |
| 66 | +// Classic streams path: createReadStream -> Transform (upper) -> createGzip |
| 67 | +// --------------------------------------------------------------------------- |
| 68 | +asyncfunctionbenchClassic(n,filesize){ |
| 69 | +awaitrunClassic(); |
| 70 | + |
| 71 | +bench.start(); |
| 72 | +lettotalBytes=0; |
| 73 | +for(leti=0;i<n;i++){ |
| 74 | +totalBytes+=awaitrunClassic(); |
| 75 | +} |
| 76 | +bench.end(totalBytes/(1024*1024)); |
| 77 | +} |
| 78 | + |
| 79 | +functionrunClassic(){ |
| 80 | +returnnewPromise((resolve,reject)=>{ |
| 81 | +constrs=fs.createReadStream(filename); |
| 82 | + |
| 83 | +constupper=newTransform({ |
| 84 | +transform(chunk,encoding,callback){ |
| 85 | +callback(null,uppercaseChunk(chunk)); |
| 86 | +}, |
| 87 | +}); |
| 88 | + |
| 89 | +constgz=zlib.createGzip(); |
| 90 | + |
| 91 | +lettotalBytes=0; |
| 92 | +constsink=newWritable({ |
| 93 | +write(chunk,encoding,callback){ |
| 94 | +totalBytes+=chunk.length; |
| 95 | +callback(); |
| 96 | +}, |
| 97 | +}); |
| 98 | + |
| 99 | +pipeline(rs,upper,gz,sink,(err)=>{ |
| 100 | +if(err)reject(err); |
| 101 | +elseresolve(totalBytes); |
| 102 | +}); |
| 103 | +}); |
| 104 | +} |
| 105 | + |
| 106 | +// --------------------------------------------------------------------------- |
| 107 | +// WebStream path: readableWebStream -> TransformStream (upper) -> CompressionStream |
| 108 | +// --------------------------------------------------------------------------- |
| 109 | +asyncfunctionbenchWebStream(n,filesize){ |
| 110 | +awaitrunWebStream(); |
| 111 | + |
| 112 | +bench.start(); |
| 113 | +lettotalBytes=0; |
| 114 | +for(leti=0;i<n;i++){ |
| 115 | +totalBytes+=awaitrunWebStream(); |
| 116 | +} |
| 117 | +bench.end(totalBytes/(1024*1024)); |
| 118 | +} |
| 119 | + |
| 120 | +asyncfunctionrunWebStream(){ |
| 121 | +constfh=awaitfs.promises.open(filename,'r'); |
| 122 | +try{ |
| 123 | +constrs=fh.readableWebStream(); |
| 124 | + |
| 125 | +constupper=newTransformStream({ |
| 126 | +transform(chunk,controller){ |
| 127 | +constbuf=newUint8Array(chunk.length); |
| 128 | +for(leti=0;i<chunk.length;i++){ |
| 129 | +constb=chunk[i]; |
| 130 | +buf[i]=(b>=0x61&&b<=0x7a) ? b-0x20 : b; |
| 131 | +} |
| 132 | +controller.enqueue(buf); |
| 133 | +}, |
| 134 | +}); |
| 135 | + |
| 136 | +constcompress=newCompressionStream('gzip'); |
| 137 | +constoutput=rs.pipeThrough(upper).pipeThrough(compress); |
| 138 | +constreader=output.getReader(); |
| 139 | + |
| 140 | +lettotalBytes=0; |
| 141 | +while(true){ |
| 142 | +const{ done, value }=awaitreader.read(); |
| 143 | +if(done)break; |
| 144 | +totalBytes+=value.byteLength; |
| 145 | +} |
| 146 | +returntotalBytes; |
| 147 | +}finally{ |
| 148 | +awaitfh.close(); |
| 149 | +} |
| 150 | +} |
| 151 | + |
| 152 | +// --------------------------------------------------------------------------- |
| 153 | +// Pull/iter path: pull() with uppercase transform + selected compression |
| 154 | +// --------------------------------------------------------------------------- |
| 155 | +asyncfunctionbenchPull(n,filesize,compression){ |
| 156 | +constiter=require('zlib/iter'); |
| 157 | + |
| 158 | +constcompressFactory={ |
| 159 | +gzip: iter.compressGzip, |
| 160 | +deflate: iter.compressDeflate, |
| 161 | +brotli: iter.compressBrotli, |
| 162 | +zstd: iter.compressZstd, |
| 163 | +}[compression]; |
| 164 | + |
| 165 | +// Warm up |
| 166 | +awaitrunPull(compressFactory); |
| 167 | + |
| 168 | +bench.start(); |
| 169 | +lettotalBytes=0; |
| 170 | +for(leti=0;i<n;i++){ |
| 171 | +totalBytes+=awaitrunPull(compressFactory); |
| 172 | +} |
| 173 | +bench.end(totalBytes/(1024*1024)); |
| 174 | +} |
| 175 | + |
| 176 | +asyncfunctionrunPull(compressFactory){ |
| 177 | +constfh=awaitfs.promises.open(filename,'r'); |
| 178 | +try{ |
| 179 | +// Stateless transform: uppercase each chunk in the batch |
| 180 | +constupper=(chunks)=>{ |
| 181 | +if(chunks===null)returnnull; |
| 182 | +constout=newArray(chunks.length); |
| 183 | +for(letj=0;j<chunks.length;j++){ |
| 184 | +out[j]=uppercaseChunk(chunks[j]); |
| 185 | +} |
| 186 | +returnout; |
| 187 | +}; |
| 188 | + |
| 189 | +constreadable=fh.pull(upper,compressFactory()); |
| 190 | + |
| 191 | +lettotalBytes=0; |
| 192 | +forawait(constchunksofreadable){ |
| 193 | +for(leti=0;i<chunks.length;i++){ |
| 194 | +totalBytes+=chunks[i].byteLength; |
| 195 | +} |
| 196 | +} |
| 197 | +returntotalBytes; |
| 198 | +}finally{ |
| 199 | +awaitfh.close(); |
| 200 | +} |
| 201 | +} |
0 commit comments