async_hooks: use typed array stack as fast path

- Communicate the current async stack length through a
  typed array field rather than a native binding method
- Add a new fixed-size `async_ids_fast_stack` typed array
  that contains the async ID stack up to a fixed limit.
  This increases performance noticeably, since most of the time
  the async ID stack will not be more than a handful of
  levels deep.
- Make the JS `pushAsyncIds()` and `popAsyncIds()` functions
  do the same thing as the native ones if the fast path
  is applicable.

Benchmarks:

    $ ./node benchmark/compare.js --new ./node --old ./node-master --runs 10 --filter next-tick process | Rscript benchmark/compare.R
    [00:03:25|% 100| 6/6 files | 20/20 runs | 1/1 configs]: Done
                                                   improvement confidence      p.value
     process/next-tick-breadth-args.js millions=4     19.72 %        *** 3.013913e-06
     process/next-tick-breadth.js millions=4          27.33 %        *** 5.847983e-11
     process/next-tick-depth-args.js millions=12      40.08 %        *** 1.237127e-13
     process/next-tick-depth.js millions=12           77.27 %        *** 1.413290e-11
     process/next-tick-exec-args.js millions=5        13.58 %        *** 1.245180e-07
     process/next-tick-exec.js millions=5             16.80 %        *** 2.961386e-07

PR-URL: https://github.com/nodejs/node/pull/17780
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Anna Henningsen 2017-12-19 15:08:18 +01:00
parent df30fd586d
commit 83e5215a4e
No known key found for this signature in database
GPG key ID: 9C63F3A6CD2AD8F9
9 changed files with 165 additions and 54 deletions

View file

@ -19,6 +19,12 @@ const async_wrap = process.binding('async_wrap');
* retrieving the triggerAsyncId value is passing directly to the
* constructor -> value set in kDefaultTriggerAsyncId -> executionAsyncId of
* the current resource.
*
* async_ids_fast_stack is a Float64Array that contains part of the async ID
* stack. Each pushAsyncIds() call adds two doubles to it, and each
* popAsyncIds() call removes two doubles from it.
* It has a fixed size, so if that is exceeded, calls to the native
* side are used instead in pushAsyncIds() and popAsyncIds().
*/
const { async_hook_fields, async_id_fields } = async_wrap;
// Store the pair executionAsyncId and triggerAsyncId in a std::stack on
@ -26,7 +32,7 @@ const { async_hook_fields, async_id_fields } = async_wrap;
// current execution stack. This is unwound as each resource exits. In the case
// of a fatal exception this stack is emptied after calling each hook's after()
// callback.
const { pushAsyncIds, popAsyncIds } = async_wrap;
const { pushAsyncIds: pushAsyncIds_, popAsyncIds: popAsyncIds_ } = async_wrap;
// For performance reasons, only track Proimses when a hook is enabled.
const { enablePromiseHook, disablePromiseHook } = async_wrap;
// Properties in active_hooks are used to keep track of the set of hooks being
@ -60,8 +66,8 @@ const active_hooks = {
// async execution. These are tracked so if the user didn't include callbacks
// for a given step, that step can bail out early.
const { kInit, kBefore, kAfter, kDestroy, kPromiseResolve,
kCheck, kExecutionAsyncId, kAsyncIdCounter,
kDefaultTriggerAsyncId } = async_wrap.constants;
kCheck, kExecutionAsyncId, kAsyncIdCounter, kTriggerAsyncId,
kDefaultTriggerAsyncId, kStackLength } = async_wrap.constants;
// Used in AsyncHook and AsyncResource.
const init_symbol = Symbol('init');
@ -329,6 +335,38 @@ function emitDestroyScript(asyncId) {
}
// This is the equivalent of the native push_async_ids() call.
function pushAsyncIds(asyncId, triggerAsyncId) {
const offset = async_hook_fields[kStackLength];
if (offset * 2 >= async_wrap.async_ids_stack.length)
return pushAsyncIds_(asyncId, triggerAsyncId);
async_wrap.async_ids_stack[offset * 2] = async_id_fields[kExecutionAsyncId];
async_wrap.async_ids_stack[offset * 2 + 1] = async_id_fields[kTriggerAsyncId];
async_hook_fields[kStackLength]++;
async_id_fields[kExecutionAsyncId] = asyncId;
async_id_fields[kTriggerAsyncId] = triggerAsyncId;
}
// This is the equivalent of the native pop_async_ids() call.
function popAsyncIds(asyncId) {
if (async_hook_fields[kStackLength] === 0) return false;
const stackLength = async_hook_fields[kStackLength];
if (async_hook_fields[kCheck] > 0 &&
async_id_fields[kExecutionAsyncId] !== asyncId) {
// Do the same thing as the native code (i.e. crash hard).
return popAsyncIds_(asyncId);
}
const offset = stackLength - 1;
async_id_fields[kExecutionAsyncId] = async_wrap.async_ids_stack[2 * offset];
async_id_fields[kTriggerAsyncId] = async_wrap.async_ids_stack[2 * offset + 1];
async_hook_fields[kStackLength] = offset;
return offset > 0;
}
module.exports = {
// Private API
getHookArrays,