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

Reply via email to