diff --git a/benchmarks/_results/Busboy_comparison-busboy-Node_12.json b/benchmarks/_results/Busboy_comparison-busboy-Node_12.json deleted file mode 100644 index 69468dd..0000000 --- a/benchmarks/_results/Busboy_comparison-busboy-Node_12.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "runtimeVersion": "12.22.7, V8 7.8.279.23-node.56", - "benchmarkName": "Busboy comparison", - "benchmarkEntryName": "busboy", - "benchmarkCycles": 10, - "benchmarkCycleSamples": 50, - "warmupCycles": 10, - "meanTimeNs": 1945927.3472222222, - "meanTimeMs": 1.9459273472222223 -} \ No newline at end of file diff --git a/benchmarks/_results/Busboy_comparison-busboy-Node_16.json b/benchmarks/_results/Busboy_comparison-busboy-Node_16.json deleted file mode 100644 index b4c492a..0000000 --- a/benchmarks/_results/Busboy_comparison-busboy-Node_16.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "runtimeVersion": "16.13.0, V8 9.4.146.19-node.13", - "benchmarkName": "Busboy comparison", - "benchmarkEntryName": "busboy", - "benchmarkCycles": 2000, - "benchmarkCycleSamples": 50, - "warmupCycles": 1000, - "meanTimeNs": 340114.0411908194, - "meanTimeMs": 0.3401140411908194 -} \ No newline at end of file diff --git a/benchmarks/_results/Busboy_comparison-busboy-Node_20.json b/benchmarks/_results/Busboy_comparison-busboy-Node_20.json new file mode 100644 index 0000000..16d8a5b --- /dev/null +++ b/benchmarks/_results/Busboy_comparison-busboy-Node_20.json @@ -0,0 +1,10 @@ +{ + "runtimeVersion": "20.9.0, V8 11.3.244.8-node.16", + "benchmarkName": "Busboy comparison", + "benchmarkEntryName": "busboy", + "benchmarkCycles": 500, + "benchmarkCycleSamples": 50, + "warmupCycles": 50, + "meanTimeNs": 259789.77117937893, + "meanTimeMs": 0.25978977117937896 +} \ No newline at end of file diff --git a/benchmarks/_results/Busboy_comparison-fastify-busboy-Node_16.json b/benchmarks/_results/Busboy_comparison-fastify-busboy-Node_16.json deleted file mode 100644 index 30f5d1e..0000000 --- a/benchmarks/_results/Busboy_comparison-fastify-busboy-Node_16.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "runtimeVersion": "16.13.0, V8 9.4.146.19-node.13", - "benchmarkName": "Busboy comparison", - "benchmarkEntryName": "fastify-busboy", - "benchmarkCycles": 2000, - "benchmarkCycleSamples": 50, - "warmupCycles": 1000, - "meanTimeNs": 270984.48082281026, - "meanTimeMs": 0.27098448082281024 -} \ No newline at end of file diff --git a/benchmarks/_results/Busboy_comparison-fastify-busboy-Node_20.json b/benchmarks/_results/Busboy_comparison-fastify-busboy-Node_20.json new file mode 100644 index 0000000..fa22c30 --- /dev/null +++ b/benchmarks/_results/Busboy_comparison-fastify-busboy-Node_20.json @@ -0,0 +1,10 @@ +{ + "runtimeVersion": "20.9.0, V8 11.3.244.8-node.16", + "benchmarkName": "Busboy comparison", + "benchmarkEntryName": "fastify-busboy", + "benchmarkCycles": 500, + "benchmarkCycleSamples": 50, + "warmupCycles": 50, + "meanTimeNs": 188173.65372222223, + "meanTimeMs": 0.18817365372222222 +} \ No newline at end of file diff --git a/benchmarks/_results/Busboy_comparison-multipasta-Node_20.json b/benchmarks/_results/Busboy_comparison-multipasta-Node_20.json new file mode 100644 index 0000000..80d3d7b --- /dev/null +++ b/benchmarks/_results/Busboy_comparison-multipasta-Node_20.json @@ -0,0 +1,10 @@ +{ + "runtimeVersion": "20.9.0, V8 11.3.244.8-node.16", + "benchmarkName": "Busboy comparison", + "benchmarkEntryName": "multipasta", + "benchmarkCycles": 500, + "benchmarkCycleSamples": 50, + "warmupCycles": 50, + "meanTimeNs": 67089.49805620349, + "meanTimeMs": 0.0670894980562035 +} \ No newline at end of file diff --git a/benchmarks/_results/results.md b/benchmarks/_results/results.md new file mode 100644 index 0000000..8a059e4 --- /dev/null +++ b/benchmarks/_results/results.md @@ -0,0 +1,7 @@ +| Node | Option | Msecs/op | Ops/sec | V8 | +| ------ | -------------- | -------------- | --------- | --------------------- | +| 20.9.0 | multipasta | 0.067089 msecs | 14905.463 | V8 11.3.244.8-node.16 | +| 20.9.0 | fastify-busboy | 0.188174 msecs | 5314.240 | V8 11.3.244.8-node.16 | +| 20.9.0 | busboy | 0.259790 msecs | 3849.266 | V8 11.3.244.8-node.16 | + +**Specs**: Coreā„¢ i9-9880H (2.3 GHz) \ No newline at end of file diff --git a/benchmarks/busboy/contestants/busboy.js b/benchmarks/busboy/contestants/busboy.js index 6cb3414..130496c 100644 --- a/benchmarks/busboy/contestants/busboy.js +++ b/benchmarks/busboy/contestants/busboy.js @@ -1,7 +1,7 @@ 'use strict' const Busboy = require('busboy') -const { buffer, boundary } = require('../data') +const { buffers, boundary } = require('../data') function process () { const busboy = Busboy({ @@ -9,27 +9,28 @@ function process () { 'content-type': 'multipart/form-data; boundary=' + boundary } }) - let processedData = '' + let processedSize = 0 + let partCount = 0 return new Promise((resolve, reject) => { busboy.on('file', (field, file, filename, encoding, mimetype) => { - // console.log('read file') + partCount++ file.on('data', (data) => { - processedData += data.toString() - // console.log(`File [${filename}] got ${data.length} bytes`); - }) - file.on('end', (fieldname) => { - // console.log(`File [${fieldname}] Finished`); + processedSize += data.length }) }) + busboy.on('field', (field, value) => { + processedSize += value.length + partCount++ + }) busboy.on('error', function (err) { reject(err) }) busboy.on('finish', function () { - resolve(processedData) + resolve([processedSize, partCount]) }) - busboy.write(buffer, () => { }) + for (const buffer of buffers) { busboy.write(buffer, () => { }) } busboy.end() }) diff --git a/benchmarks/busboy/contestants/fastify-busboy.js b/benchmarks/busboy/contestants/fastify-busboy.js index 6750f77..64be2a8 100644 --- a/benchmarks/busboy/contestants/fastify-busboy.js +++ b/benchmarks/busboy/contestants/fastify-busboy.js @@ -1,7 +1,7 @@ 'use strict' const Busboy = require('../../../lib/main') -const { buffer, boundary } = require('../data') +const { buffers, boundary } = require('../data') function process () { const busboy = new Busboy({ @@ -10,27 +10,29 @@ function process () { } }) - let processedData = '' + let processedSize = 0 + let partCount = 0 return new Promise((resolve, reject) => { busboy.on('file', (field, file, filename, encoding, mimetype) => { - // console.log('read file') + partCount++ file.on('data', (data) => { - processedData += data.toString() - // console.log(`File [${filename}] got ${data.length} bytes`); - }) - file.on('end', (fieldname) => { - // console.log(`File [${fieldname}] Finished`); + processedSize += data.length }) }) + busboy.on('field', (field, value) => { + processedSize += value.length + partCount++ + }) busboy.on('error', function (err) { reject(err) }) busboy.on('finish', function () { - resolve(processedData) + resolve([processedSize, partCount]) }) - busboy.write(buffer, () => { }) + + for (const buffer of buffers) { busboy.write(buffer, () => { }) } busboy.end() }) diff --git a/benchmarks/busboy/contestants/multipasta.js b/benchmarks/busboy/contestants/multipasta.js new file mode 100644 index 0000000..d0e2866 --- /dev/null +++ b/benchmarks/busboy/contestants/multipasta.js @@ -0,0 +1,40 @@ +'use strict' + +const MP = require('multipasta') +const { buffers, boundary } = require('../data') + +function process () { + return new Promise((resolve, reject) => { + let processedSize = 0 + let partCount = 0 + const parser = MP.make({ + headers: { + 'content-type': 'multipart/form-data; boundary=' + boundary + }, + onField (_info, value) { + processedSize += value.length + partCount++ + }, + onFile (_info) { + partCount++ + return function (chunk) { + if (chunk !== null) { + processedSize += chunk.length + } + } + }, + onError (err) { + reject(err) + }, + onDone () { + resolve([processedSize, partCount]) + } + }) + for (const buffer of buffers) { parser.write(buffer) } + parser.end() + }) +} + +module.exports = { + process +} diff --git a/benchmarks/busboy/data.js b/benchmarks/busboy/data.js index 4fdefae..3a8ac15 100644 --- a/benchmarks/busboy/data.js +++ b/benchmarks/busboy/data.js @@ -1,8 +1,13 @@ 'use strict' const boundary = '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k' -const randomContent = Buffer.from(makeString(1024 * 500), 'utf8') -const buffer = createMultipartBuffer(boundary) +const fileContent = Buffer.from(makeString(1024 * 512), 'utf8') +const fileCount = 2 +const fieldContent = Buffer.from(makeString(128), 'utf8') +const fieldCount = 10 +const chunkSize = 16000 + +const buffers = createMultipartBuffer(boundary) function makeString (length) { let result = '' @@ -17,18 +22,38 @@ function makeString (length) { function createMultipartBuffer (boundary) { const payload = [ - '--' + boundary, - 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', - 'Content-Type: application/octet-stream', - '', - randomContent, + /* eslint-disable no-array-constructor */ + ...Array.from({ length: fieldCount }, (_, i) => [ + '--' + boundary, + `Content-Disposition: form-data; name="field_${i}"`, + 'Content-Type: application/octet-stream', + '', + fieldContent + ]).flat(), + /* eslint-disable no-array-constructor */ + ...Array.from({ length: fileCount }, (_, i) => [ + '--' + boundary, + `Content-Disposition: form-data; name="file_${i}"; filename="file.dat"`, + 'Content-Type: application/octet-stream', + '', + fileContent + ]).flat(), '--' + boundary + '--' ].join('\r\n') - return Buffer.from(payload, 'ascii') + const buf = Buffer.from(payload, 'ascii') + // split into 16000 byte chunks to simulate network packets + const buffers = [] + for (let i = 0; i < buf.length; i += chunkSize) { + buffers.push(buf.subarray(i, i + chunkSize)) + } + return buffers } module.exports = { boundary, - buffer, - randomContent + buffers, + fileContent, + fileCount, + fieldContent, + fieldCount } diff --git a/benchmarks/busboy/executioner.js b/benchmarks/busboy/executioner.js index 524912c..7efcd47 100644 --- a/benchmarks/busboy/executioner.js +++ b/benchmarks/busboy/executioner.js @@ -2,6 +2,7 @@ const { process: processBusboy } = require('./contestants/busboy') const { process: processFastify } = require('./contestants/fastify-busboy') +const { process: processMultipasta } = require('./contestants/multipasta') const { getCommonBuilder } = require('../common/commonBuilder') const { validateAccuracy } = require('./validator') const { resolveContestant } = require('../common/contestantResolver') @@ -9,7 +10,8 @@ const { outputResults } = require('../common/resultUtils') const contestants = { busboy: measureBusboy, - fastify: measureFastify + fastify: measureFastify, + multipasta: measureMultipasta } async function measureBusboy () { @@ -32,6 +34,16 @@ async function measureFastify () { outputResults(benchmark, benchmarkResults) } +async function measureMultipasta () { + const benchmark = getCommonBuilder() + .benchmarkName('Busboy comparison') + .benchmarkEntryName('multipasta') + .asyncFunctionUnderTest(processMultipasta) + .build() + const benchmarkResults = await benchmark.executeAsync() + outputResults(benchmark, benchmarkResults) +} + function execute () { return validateAccuracy(processBusboy()) .then(() => { @@ -43,7 +55,7 @@ function execute () { }).then(() => { console.log('all done') }).catch((err) => { - console.error(`Something went wrong: ${err.message}`) + console.error('Something went wrong', err) }) } diff --git a/benchmarks/busboy/validator.js b/benchmarks/busboy/validator.js index a86cc33..19953b4 100644 --- a/benchmarks/busboy/validator.js +++ b/benchmarks/busboy/validator.js @@ -1,13 +1,14 @@ 'use strict' const { validateEqual } = require('validation-utils') -const { randomContent } = require('./data') +const { fileContent, fileCount, fieldContent, fieldCount } = require('./data') -const EXPECTED_RESULT = randomContent.toString() +const EXPECTED_RESULT = (fileContent.length * fileCount) + (fieldContent.length * fieldCount) async function validateAccuracy (actualResultPromise) { - const result = await actualResultPromise + const [result, parts] = await actualResultPromise validateEqual(result, EXPECTED_RESULT) + validateEqual(parts, fileCount + fieldCount) } module.exports = { diff --git a/benchmarks/common/commonBuilder.js b/benchmarks/common/commonBuilder.js index b5707aa..3f143d2 100644 --- a/benchmarks/common/commonBuilder.js +++ b/benchmarks/common/commonBuilder.js @@ -13,32 +13,28 @@ const options = getopts(process.argv.slice(1), { const PRESET = { LOW: (builder) => { - return builder - .warmupCycles(1000) - .benchmarkCycles(1000) + return builder.warmupCycles(5).benchmarkCycles(50) }, MEDIUM: (builder) => { - return builder - .warmupCycles(1000) - .benchmarkCycles(2000) + return builder.warmupCycles(10).benchmarkCycles(100) }, HIGH: (builder) => { - return builder - .warmupCycles(1000) - .benchmarkCycles(10000) + return builder.warmupCycles(50).benchmarkCycles(500) } } function getCommonBuilder () { const presetId = options.preset || 'MEDIUM' - const preset = validateNotNil(PRESET[presetId.toUpperCase()], `Unknown preset: ${presetId}`) + const preset = validateNotNil( + PRESET[presetId.toUpperCase()], + `Unknown preset: ${presetId}` + ) const builder = new BenchmarkBuilder() preset(builder) - return builder - .benchmarkCycleSamples(50) + return builder.benchmarkCycleSamples(50) } module.exports = { diff --git a/benchmarks/package.json b/benchmarks/package.json index 2574b8b..21aa0aa 100644 --- a/benchmarks/package.json +++ b/benchmarks/package.json @@ -13,9 +13,10 @@ "install-node": "nvm install 17.2.0 && nvm install 16.13.1 && nvm install 14.18.2 && nvm install 12.22.7", "benchmark-busboy": "node busboy/executioner.js -c 0", "benchmark-fastify": "node busboy/executioner.js -c 1", - "benchmark-all": "npm run benchmark-busboy -- -p high && npm run benchmark-fastify -- -p high", - "benchmark-all-medium": "npm run benchmark-busboy -- -p medium && npm run benchmark-fastify -- -p medium", - "benchmark-all-low": "npm run benchmark-busboy -- -p low && npm run benchmark-fastify -- -p low", + "benchmark-multipasta": "node busboy/executioner.js -c 2", + "benchmark-all": "npm run benchmark-busboy -- -p high && npm run benchmark-fastify -- -p high && npm run benchmark-multipasta -- -p high", + "benchmark-all-medium": "npm run benchmark-busboy -- -p medium && npm run benchmark-fastify -- -p medium && npm run benchmark-multipasta -- -p medium", + "benchmark-all-low": "npm run benchmark-busboy -- -p low && npm run benchmark-fastify -- -p low && npm run benchmark-multipasta -- -p low", "combine-results": "node common/resultsCombinator.js -r _results -p 6" } } diff --git a/package.json b/package.json index 4be895c..8711ce0 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "devDependencies": { "@types/node": "^20.1.0", "busboy": "^1.0.0", + "multipasta": "^0.1.15", "photofinish": "^1.8.0", "snazzy": "^9.0.0", "standard": "^17.0.0",