From 65dd60f124b67db701a10dd918e5adbc8b7edb7e Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Sun, 7 Apr 2024 22:32:02 +0300 Subject: [PATCH 1/3] lib: improve perf of `AbortListener` creation --- lib/internal/abort_controller.js | 75 ++++++++++++++++---------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/lib/internal/abort_controller.js b/lib/internal/abort_controller.js index b10fb35a1a6e4f..2d9aabb2c35d3e 100644 --- a/lib/internal/abort_controller.js +++ b/lib/internal/abort_controller.js @@ -6,7 +6,6 @@ const { ObjectAssign, ObjectDefineProperties, - ObjectSetPrototypeOf, ObjectDefineProperty, PromiseResolve, SafeFinalizationRegistry, @@ -69,6 +68,8 @@ const { let _MessageChannel; let markTransferMode; +const kDontThrowSymbol = Symbol('kDontThrowSymbol'); + // Loading the MessageChannel and markTransferable have to be done lazily // because otherwise we'll end up with a require cycle that ends up with // an incomplete initialization of abort_controller. @@ -137,8 +138,35 @@ function setWeakAbortSignalTimeout(weakRef, delay) { } class AbortSignal extends EventTarget { - constructor() { - throw new ERR_ILLEGAL_CONSTRUCTOR(); + + /** + * @param {symbol | undefined} dontThrowSymbol + * @param {{ + * aborted? : boolean, + * reason? : any, + * transferable? : boolean, + * composite? : boolean, + * }} [init] + * @private + */ + constructor(dontThrowSymbol = undefined, init = kEmptyObject) { + if (dontThrowSymbol !== kDontThrowSymbol) { + throw new ERR_ILLEGAL_CONSTRUCTOR(); + } + super(); + + const { + aborted = false, + reason = undefined, + transferable = false, + composite = false, + } = init; + this[kAborted] = aborted; + this[kReason] = reason; + this[kComposite] = composite; + if (transferable) { + lazyMarkTransferMode(this, false, true); + } } /** @@ -176,7 +204,7 @@ class AbortSignal extends EventTarget { */ static abort( reason = new DOMException('This operation was aborted', 'AbortError')) { - return createAbortSignal({ aborted: true, reason }); + return new AbortSignal(kDontThrowSymbol, { aborted: true, reason }); } /** @@ -185,7 +213,7 @@ class AbortSignal extends EventTarget { */ static timeout(delay) { validateUint32(delay, 'delay', false); - const signal = createAbortSignal(); + const signal = new AbortSignal(kDontThrowSymbol); signal[kTimeout] = true; clearTimeoutRegistry.register( signal, @@ -199,7 +227,7 @@ class AbortSignal extends EventTarget { */ static any(signals) { validateAbortSignalArray(signals, 'signals'); - const resultSignal = createAbortSignal({ composite: true }); + const resultSignal = new AbortSignal(kDontThrowSymbol, { composite: true }); if (!signals.length) { return resultSignal; } @@ -319,7 +347,7 @@ class AbortSignal extends EventTarget { } function ClonedAbortSignal() { - return createAbortSignal({ transferable: true }); + return new AbortSignal(kDontThrowSymbol, { transferable: true }); } ClonedAbortSignal.prototype[kDeserialize] = () => {}; @@ -337,33 +365,6 @@ ObjectDefineProperty(AbortSignal.prototype, SymbolToStringTag, { defineEventHandler(AbortSignal.prototype, 'abort'); -/** - * @param {{ - * aborted? : boolean, - * reason? : any, - * transferable? : boolean, - * composite? : boolean, - * }} [init] - * @returns {AbortSignal} - */ -function createAbortSignal(init = kEmptyObject) { - const { - aborted = false, - reason = undefined, - transferable = false, - composite = false, - } = init; - const signal = new EventTarget(); - ObjectSetPrototypeOf(signal, AbortSignal.prototype); - signal[kAborted] = aborted; - signal[kReason] = reason; - signal[kComposite] = composite; - if (transferable) { - lazyMarkTransferMode(signal, false, true); - } - return signal; -} - function abortSignal(signal, reason) { if (signal[kAborted]) return; signal[kAborted] = true; @@ -385,7 +386,7 @@ class AbortController { * @type {AbortSignal} */ get signal() { - this.#signal ??= createAbortSignal(); + this.#signal ??= new AbortSignal(kDontThrowSymbol); return this.#signal; } @@ -393,7 +394,7 @@ class AbortController { * @param {any} [reason] */ abort(reason = new DOMException('This operation was aborted', 'AbortError')) { - abortSignal(this.#signal ??= createAbortSignal(), reason); + abortSignal(this.#signal ??= new AbortSignal(kDontThrowSymbol), reason); } [customInspectSymbol](depth, options) { @@ -404,7 +405,7 @@ class AbortController { static [kMakeTransferable]() { const controller = new AbortController(); - controller.#signal = createAbortSignal({ transferable: true }); + controller.#signal = new AbortSignal(kDontThrowSymbol, { transferable: true }); return controller; } } From 1952037fc3950f3aeabe03c3827570006ea95e53 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Sun, 7 Apr 2024 22:49:56 +0300 Subject: [PATCH 2/3] benchmark: add AbortSignal.abort benchmarks --- .../abort-signal-static-abort.js | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 benchmark/abort_controller/abort-signal-static-abort.js diff --git a/benchmark/abort_controller/abort-signal-static-abort.js b/benchmark/abort_controller/abort-signal-static-abort.js new file mode 100644 index 00000000000000..2b1c884a260780 --- /dev/null +++ b/benchmark/abort_controller/abort-signal-static-abort.js @@ -0,0 +1,29 @@ +'use strict'; +const common = require('../common.js'); + +const bench = common.createBenchmark(main, { + n: [5e6], + kind: ['default-reason', 'same-reason'], +}); + +function main({ n, kind }) { + switch (kind) { + case 'default-reason': + bench.start(); + for (let i = 0; i < n; ++i) + AbortSignal.abort(); + bench.end(n); + break; + case 'same-reason': { + const reason = new Error('same reason'); + + bench.start(); + for (let i = 0; i < n; ++i) + AbortSignal.abort(reason); + bench.end(n); + break; + } + default: + throw new Error('Invalid kind'); + } +} From 245433110b68415e3ea9c2de3c719df86b10b803 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Sun, 7 Apr 2024 23:14:00 +0300 Subject: [PATCH 3/3] events: improve creation time of EventTarget --- lib/internal/event_target.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/internal/event_target.js b/lib/internal/event_target.js index d1b0af7317085d..c063a453db7e45 100644 --- a/lib/internal/event_target.js +++ b/lib/internal/event_target.js @@ -541,6 +541,7 @@ class Listener { } } +// Updating this function will require updating the EventTarget as well function initEventTarget(self) { self[kEvents] = new SafeMap(); self[kMaxEventTargetListeners] = EventEmitter.defaultMaxListeners; @@ -554,9 +555,12 @@ class EventTarget { // Ref: https://github.com/nodejs/node/pull/33661 static [kIsEventTarget] = true; - constructor() { - initEventTarget(this); - } + // From here: Same as initEventTarget code, added here due to performance reasons + [kEvents] = new SafeMap(); + [kMaxEventTargetListeners] = EventEmitter.defaultMaxListeners; + [kMaxEventTargetListenersWarned] = false; + [kHandlers] = new SafeMap(); + // Until here [kNewListener](size, type, listener, once, capture, passive, weak) { if (this[kMaxEventTargetListeners] > 0 && @@ -868,6 +872,7 @@ ObjectDefineProperties(EventTarget.prototype, { }, }); +// If you update this function please add to node event target as well, not used in NodeEventTarget due to performance reasons function initNodeEventTarget(self) { initEventTarget(self); } @@ -876,11 +881,6 @@ class NodeEventTarget extends EventTarget { static [kIsNodeEventTarget] = true; static defaultMaxListeners = 10; - constructor() { - super(); - initNodeEventTarget(this); - } - /** * @param {number} n */