mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 21:58:48 +02:00
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:
parent
df30fd586d
commit
83e5215a4e
9 changed files with 165 additions and 54 deletions
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue