1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-11 01:58:05 -05:00
denoland-deno/ext/node/polyfills/_next_tick.ts
Divy Srivastava 0710af034f
perf: avoid multiple calls to runMicrotask (#26378)
Improves HTTP throughput by 8-9k rps on Linux:

this patch
```
Requests/sec: 145001.69
Transfer/sec:     20.74MB
```

main
```
Requests/sec: 137866.61
Transfer/sec:     19.72MB
```

The improvements comes from the reduced number of calls to
`op_run_microtask` per request. Returning `true` from a macrotask
callback already calls `op_run_microtask` so the extra call was
redundant.

Here's `--strace-ops` output for a single request:

main
```
[     4.667] op_http_wait                                       : CompletedAsync Async
[     4.667] op_run_microtasks                                  : Dispatched Slow
[     4.668] op_http_try_wait                                   : Dispatched Slow
[     4.668] op_http_try_wait                                   : Completed Slow
[     4.668] op_http_wait                                       : Dispatched Async
[     4.668] op_http_set_response_header                        : Dispatched Slow
[     4.668] op_http_set_response_header                        : Completed Slow
[     4.669] op_http_set_response_body_text                     : Dispatched Slow
[     4.669] op_http_set_response_body_text                     : Completed Slow
[     4.669] op_run_microtasks                                  : Completed Slow
[     4.669] op_has_tick_scheduled                              : Dispatched Slow
[     4.669] op_has_tick_scheduled                              : Completed Slow
[     4.669] op_run_microtasks                                  : Dispatched Slow
[     4.669] op_run_microtasks                                  : Completed Slow
[     4.669] op_run_microtasks                                  : Dispatched Slow
[     4.669] op_run_microtasks                                  : Completed Slow
```

this pr
```
[     3.726] op_http_wait                                       : CompletedAsync Async
[     3.727] op_run_microtasks                                  : Dispatched Slow
[     3.727] op_http_try_wait                                   : Dispatched Slow
[     3.727] op_http_try_wait                                   : Completed Slow
[     3.727] op_http_wait                                       : Dispatched Async
[     3.727] op_http_set_response_header                        : Dispatched Slow
[     3.728] op_http_set_response_header                        : Completed Slow
[     3.728] op_http_set_response_body_text                     : Dispatched Slow
[     3.728] op_http_set_response_body_text                     : Completed Slow
[     3.728] op_run_microtasks                                  : Completed Slow
[     3.728] op_run_microtasks                                  : Dispatched Slow
[     3.728] op_run_microtasks                                  : Completed Slow
```
2024-10-19 08:42:59 +05:30

162 lines
4.6 KiB
TypeScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// Copyright Joyent, Inc. and other Node contributors.
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
import { core } from "ext:core/mod.js";
import { validateFunction } from "ext:deno_node/internal/validators.mjs";
import { _exiting } from "ext:deno_node/_process/exiting.ts";
import { FixedQueue } from "ext:deno_node/internal/fixed_queue.ts";
const {
getAsyncContext,
setAsyncContext,
} = core;
interface Tock {
callback: (...args: Array<unknown>) => void;
args: Array<unknown>;
snapshot: unknown;
}
let nextTickEnabled = false;
export function enableNextTick() {
nextTickEnabled = true;
}
const queue = new FixedQueue();
export function processTicksAndRejections() {
let tock: Tock;
do {
// deno-lint-ignore no-cond-assign
while (tock = queue.shift()) {
// FIXME(bartlomieju): Deno currently doesn't support async hooks
// const asyncId = tock[async_id_symbol];
// emitBefore(asyncId, tock[trigger_async_id_symbol], tock);
const oldContext = getAsyncContext();
try {
setAsyncContext(tock.snapshot);
const callback = tock.callback;
if (tock.args === undefined) {
callback();
} else {
const args = (tock as Tock).args;
switch (args.length) {
case 1:
callback(args[0]);
break;
case 2:
callback(args[0], args[1]);
break;
case 3:
callback(args[0], args[1], args[2]);
break;
case 4:
callback(args[0], args[1], args[2], args[3]);
break;
default:
callback(...args);
}
}
} finally {
// FIXME(bartlomieju): Deno currently doesn't support async hooks
// if (destroyHooksExist())
// emitDestroy(asyncId);
setAsyncContext(oldContext);
}
// FIXME(bartlomieju): Deno currently doesn't support async hooks
// emitAfter(asyncId);
}
core.runMicrotasks();
// FIXME(bartlomieju): Deno currently doesn't unhandled rejections
// } while (!queue.isEmpty() || processPromiseRejections());
} while (!queue.isEmpty());
core.setHasTickScheduled(false);
// FIXME(bartlomieju): Deno currently doesn't unhandled rejections
// setHasRejectionToWarn(false);
}
export function runNextTicks() {
// FIXME(bartlomieju): Deno currently doesn't unhandled rejections
// if (!hasTickScheduled() && !hasRejectionToWarn())
// runMicrotasks();
// if (!hasTickScheduled() && !hasRejectionToWarn())
// return;
if (queue.isEmpty() || !core.hasTickScheduled()) {
return true;
}
processTicksAndRejections();
return true;
}
// `nextTick()` will not enqueue any callback when the process is about to
// exit since the callback would not have a chance to be executed.
export function nextTick(this: unknown, callback: () => void): void;
export function nextTick<T extends Array<unknown>>(
this: unknown,
callback: (...args: T) => void,
...args: T
): void;
export function nextTick<T extends Array<unknown>>(
this: unknown,
callback: (...args: T) => void,
...args: T
) {
// If we're snapshotting we don't want to push nextTick to be run. We'll
// enable next ticks in "__bootstrapNodeProcess()";
if (!nextTickEnabled) {
return;
}
validateFunction(callback, "callback");
if (_exiting) {
return;
}
// TODO(bartlomieju): seems superfluous if we don't depend on `arguments`
let args_;
switch (args.length) {
case 0:
break;
case 1:
args_ = [args[0]];
break;
case 2:
args_ = [args[0], args[1]];
break;
case 3:
args_ = [args[0], args[1], args[2]];
break;
default:
args_ = new Array(args.length);
for (let i = 0; i < args.length; i++) {
args_[i] = args[i];
}
}
if (queue.isEmpty()) {
core.setHasTickScheduled(true);
}
// FIXME(bartlomieju): Deno currently doesn't support async hooks
// const asyncId = newAsyncId();
// const triggerAsyncId = getDefaultTriggerAsyncId();
const tickObject = {
// FIXME(bartlomieju): Deno currently doesn't support async hooks
// [async_id_symbol]: asyncId,
// [trigger_async_id_symbol]: triggerAsyncId,
snapshot: getAsyncContext(),
callback,
args: args_,
};
// FIXME(bartlomieju): Deno currently doesn't support async hooks
// if (initHooksExist())
// emitInit(asyncId, 'TickObject', triggerAsyncId, tickObject);
queue.push(tickObject);
}