Nice... And not really surprising. I am slightly surprised async/await is so close to promises. Which means that improving promises performance should probably be a priority. I still feel the easier to reason with code is well worth it, given many apps now scale horizontally.
On Sun, Apr 29, 2018, 10:31 kai zhu <kaizhu...@gmail.com> wrote: > fyi, here are some benchmark results of nodejs' client-based http-request > throughput, employing various async-design-patterns (on a 4gb linode box). > overall, recursive-callbacks seem to ~15% faster than both async/await and > promises (~3000 vs ~2600 client-http-request/s). > > ```shell > $ REQUESTS_PER_TICK=10 node example.js > > state 1 - node (v9.11.1) > state 2 - http-server listening on port 3000 > ... > state 3 - clientHttpRequestWithRecursiveCallback - flooding http-server > with request "http://localhost:3000" > state 5 - clientHttpRequestWithRecursiveCallback - testRun #99 > state 5 - clientHttpRequestWithRecursiveCallback - requestsTotal = 14690 > (in 5009 ms) > state 5 - clientHttpRequestWithRecursiveCallback - requestsPassed = 7349 > state 5 - clientHttpRequestWithRecursiveCallback - requestsFailed = 7341 ({ > "statusCode - 500": true > }) > state 5 - clientHttpRequestWithRecursiveCallback - 2933 requests / second > state 5 - mean requests / second = { > "clientHttpRequestWithRecursiveCallback": "3059 (156 sigma)", > "clientHttpRequestWithPromise": "2615 (106 sigma)", > "clientHttpRequestWithAsyncAwait": "2591 (71 sigma)" > } > ``` > > > you can reproduce the benchmark-results by running this > zero-dependency/zero-config, standalone nodejs script below: > > > ```js > /* > * example.js > * > * this zero-dependency example will benchmark nodejs' client-based > http-requests throughput, > * using recursive-callback/promise/async-await design-patterns. > * > * the program will make 100 test-runs (randomly picking a design-pattern > per test-run), > * measuring client-based http-requests/seconde over a 5000 ms interval. > * it will save the 16 most recent test-runs for each design-pattern, > * and print the mean and standard deviation. > * any test-run with unusual errors (timeouts, econnreset, etc), > * will be discarded and not used in calculations > * > * the script accepts one env variable $REQUESTS_PER_TICK, which defaults > to 10 > * (you can try increasing it if you have a high-performance machine) > * > * > * > * example usage: > * $ REQUESTS_PER_TICK=10 node example.js > * > * example output: > * > * state 1 - node (v9.11.1) > * state 2 - http-server listening on port 3000 > * ... > * state 3 - clientHttpRequestWithRecursiveCallback - flooding http-server > with request "http://localhost:3000" > * state 5 - clientHttpRequestWithRecursiveCallback - testRun #99 > * state 5 - clientHttpRequestWithRecursiveCallback - requestsTotal = > 14690 (in 5009 ms) > * state 5 - clientHttpRequestWithRecursiveCallback - requestsPassed = 7349 > * state 5 - clientHttpRequestWithRecursiveCallback - requestsFailed = > 7341 ({ > * "statusCode - 500": true > * }) > * state 5 - clientHttpRequestWithRecursiveCallback - 2933 requests / > second > * state 5 - mean requests / second = { > * "clientHttpRequestWithRecursiveCallback": "3059 (156 sigma)", > * "clientHttpRequestWithPromise": "2615 (106 sigma)", > * "clientHttpRequestWithAsyncAwait": "2591 (71 sigma)" > * } > * > * state 6 - process.exit(0) > */ > > /*jslint > bitwise: true, > browser: true, > maxerr: 4, > maxlen: 100, > node: true, > nomen: true, > regexp: true, > stupid: true > */ > > (function () { > 'use strict'; > var local; > local = {}; > > // require modules > local.http = require('http'); > local.url = require('url'); > > /* jslint-ignore-begin */ > local.clientHttpRequestWithAsyncAwait = async function (url, onError) { > /* > * this function will make an http-request using async/await > design-pattern > */ > var request, response, timerTimeout; > try { > response = await new Promise(function (resolve, reject) { > // init timeout > timerTimeout = setTimeout(function () { > reject(new Error('timeout - 2000 ms')); > }, 2000); > request = local.http.request(local.url.parse(url), > resolve); > request.on('error', reject); > request.end(); > }); > await new Promise(function (resolve, reject) { > // ignore stream-data > response.on('data', local.nop); > if (response.statusCode >= 400) { > reject(new Error('statusCode - ' + > response.statusCode)); > return; > } > response.on('end', resolve); > response.on('error', reject); > }); > } catch (error) { > // cleanup timerTimeout > clearTimeout(timerTimeout); > // cleanup request and response > if (request) { > request.destroy(); > } > if (response) { > response.destroy(); > } > onError(error); > return; > } > onError(); > }; > /* jslint-ignore-end */ > > local.clientHttpRequestWithPromise = function (url, onError) { > /* > * this function will make an http-request using promise design-pattern > */ > var request, response, timerTimeout; > new Promise(function (resolve, reject) { > // init timeout > timerTimeout = setTimeout(function () { > reject(new Error('timeout - 2000 ms')); > }, 2000); > request = local.http.request(local.url.parse(url), resolve); > request.on('error', reject); > request.end(); > }).then(function (result) { > return new Promise(function (resolve, reject) { > response = result; > // ignore stream-data > response.on('data', local.nop); > if (response.statusCode >= 400) { > reject(new Error('statusCode - ' + > response.statusCode)); > return; > } > response.on('end', resolve); > response.on('error', reject); > }); > }).then(onError).catch(function (error) { > // cleanup timerTimeout > clearTimeout(timerTimeout); > // cleanup request and response > if (request) { > request.destroy(); > } > if (response) { > response.destroy(); > } > onError(error); > }); > }; > > local.clientHttpRequestWithRecursiveCallback = function (url, onError) > { > /* > * this function will make an http-request using recursive-callback > design-pattern > */ > var isDone, modeNext, request, response, onNext, timerTimeout; > onNext = function (error) { > modeNext += error instanceof Error > ? Infinity > : 1; > switch (modeNext) { > case 1: > // init timeout > timerTimeout = setTimeout(function () { > onNext(new Error('timeout - 2000 ms')); > }, 2000); > request = local.http.request(local.url.parse(url), onNext); > request.on('error', onNext); > request.end(); > break; > case 2: > response = error; > // ignore stream-data > response.on('data', local.nop); > if (response.statusCode >= 400) { > onNext(new Error('statusCode - ' + > response.statusCode)); > } > response.on('end', onNext); > response.on('error', onNext); > break; > default: > if (isDone) { > return; > } > // cleanup timerTimeout > clearTimeout(timerTimeout); > // cleanup request and response > if (request) { > request.destroy(); > } > if (response) { > response.destroy(); > } > isDone = true; > onError(error); > } > }; > modeNext = 0; > onNext(); > }; > > local.clientHttpRequestOnError = function (error) { > /* > * this function is the callback for clientHttpRequest > */ > if (error) { > local.errorDict[error.message] = true; > local.requestsFailed += 1; > } else { > local.requestsPassed += 1; > } > if (local.timeElapsed >= 5000 && > (local.requestsFailed + local.requestsPassed) === > local.requestsTotal) { > local.main(); > } > }; > > local.nop = function () { > /* > * this function will do nothing > */ > return; > }; > > local.templateRenderAndPrint = function (template) { > /* > * this function render simple double-mustache templates with the > local dict, > * and print to stderr > */ > console.error(template.replace((/\{\{.*?\}\}/g), function (match0) > { > return local[match0.slice(2, -2)]; > })); > }; > > local.main = function (error) { > /* > * this function will fun the main-loop > */ > local.state += error > ? Infinity > : 1; > switch (local.state) { > case 1: > // init local var > local.clientHttpRequestUrl = 'http://localhost:3000'; > local.version = process.version; > local.templateRenderAndPrint('state {{state}} - node > ({{version}})'); > // create simple http-server that responds with random 200 or > 500 statusCode > local.http.createServer(function (request, response) { > request > // ignore stream-data > .on('data', local.nop) > .on('error', console.error); > // respond randomly with either 200 or 500 statusCode > response.statusCode = Math.random() < 0.5 > ? 200 > : 500; > response > .on('error', console.error) > .end(); > // listen on port 3000 > }).listen(3000, local.main); > break; > case 2: > local.templateRenderAndPrint('state {{state}} - http-server > listening on port 3000'); > local.main(); > break; > case 3: > local.clientHttpRequestState = local.clientHttpRequestState || > 0; > local.clientHttpRequestState += 1; > if (local.clientHttpRequestState < 100) { > switch (Math.floor(Math.random() * 3)) { > case 0: > local.clientHttpRequest = > 'clientHttpRequestWithAsyncAwait'; > break; > case 1: > local.clientHttpRequest = > 'clientHttpRequestWithPromise'; > break; > case 2: > local.clientHttpRequest = > 'clientHttpRequestWithRecursiveCallback'; > break; > } > } else { > local.state += 2; > local.main(); > return; > } > local.templateRenderAndPrint('\nstate {{state}} - > {{clientHttpRequest}} - ' + > 'flooding http-server with request > "{{clientHttpRequestUrl}}"'); > local.errorDict = {}; > local.requestsFailed = 0; > local.requestsPassed = 0; > local.requestsTotal = 0; > local.timeElapsed = 0; > local.timeStart = Date.now(); > local.main(); > break; > case 4: > setTimeout(function () { > for (local.ii = 0; > // configurable REQUESTS_PER_TICK > local.ii < (Number(process.env.REQUESTS_PER_TICK) > || 10); > local.ii += 1) { > local.requestsTotal += 1; > local[local.clientHttpRequest]( > local.clientHttpRequestUrl, > local.clientHttpRequestOnError > ); > } > // recurse / repeat this step for 5000 ms > local.timeElapsed = Date.now() - local.timeStart; > if (local.timeElapsed < 5000) { > local.state -= 1; > local.main(); > } > }); > break; > case 5: > local.timeElapsed = Date.now() - local.timeStart; > local.requestsPerSecond = Math.round(1000 * > local.requestsTotal / local.timeElapsed); > local.errorDictJson = JSON.stringify(local.errorDict, null, 4); > local.resultList = local.resultList || {}; > local.resultMean = local.resultMean || {}; > // only save result if no unusual errors occurred > if (Object.keys(local.errorDict).length <= 1) { > local.resultList[local.clientHttpRequest] = > local.resultList[local.clientHttpRequest] || []; > > local.resultList[local.clientHttpRequest].push(local.requestsPerSecond); > // remove old data > if (local.resultList[local.clientHttpRequest].length > 16) > { > local.resultList[local.clientHttpRequest].shift(); > } > // calculate mean > local.resultMean[local.clientHttpRequest] = Math.round( > > local.resultList[local.clientHttpRequest].reduce(function (aa, bb) { > return aa + (bb || 0); > }, 0) / > local.resultList[local.clientHttpRequest].length > ); > // calculate sigma > local.resultMean[local.clientHttpRequest] += ' (' + > Math.round(Math.sqrt( > > local.resultList[local.clientHttpRequest].reduce(function (aa, bb) { > return aa + Math.pow( > (bb || 0) - > local.resultMean[local.clientHttpRequest], > 2 > ); > }, 0) / > (local.resultList[local.clientHttpRequest].length - 1) > )) + ' sigma)'; > } > local.resultJson = JSON.stringify(local.resultMean, null, 4); > local.templateRenderAndPrint( > /* jslint-ignore-begin */ > '\ > state {{state}} - {{clientHttpRequest}} - testRun > #{{clientHttpRequestState}}\n\ > state {{state}} - {{clientHttpRequest}} - requestsTotal = > {{requestsTotal}} (in {{timeElapsed}} ms)\n\ > state {{state}} - {{clientHttpRequest}} - requestsPassed = > {{requestsPassed}}\n\ > state {{state}} - {{clientHttpRequest}} - requestsFailed = > {{requestsFailed}} ({{errorDictJson}})\n\ > state {{state}} - {{clientHttpRequest}} - {{requestsPerSecond}} requests / > second\n\ > state {{state}} - mean requests / second = {{resultJson}}\n\ > ', > /* jslint-ignore-end */ > ); > // repeat test with other design-patterns > local.state -= 3; > local.main(); > break; > default: > if (error) { > console.error(error); > } > local.exitCode = Number(!!error); > local.templateRenderAndPrint('state {{state}} - > process.exit({{exitCode}})'); > process.exit(local.exitCode); > } > }; > // run main-loop > local.state = 0; > local.main(); > }()); > ``` > > kai zhu > kaizhu...@gmail.com > > > > _______________________________________________ > es-discuss mailing list > es-discuss@mozilla.org > https://mail.mozilla.org/listinfo/es-discuss >
_______________________________________________ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss