Re: performance benchmark of async-design-patterns - recursive-callbacks vs. promises vs. async/await
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 zhuwrote: > 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
performance benchmark of async-design-patterns - recursive-callbacks vs. promises vs. async/await
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,