diff options
author | Aaron O'Mullan <aaron.omullan@gmail.com> | 2021-04-07 14:38:54 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-07 14:38:54 +0200 |
commit | 2865f39bec6da135a2d2d679a65e7ff139131bd7 (patch) | |
tree | ea575143149a97815db7942898074d644b095ecc /core/core.js | |
parent | de641838831a90b142eceab5aa9df269084d83cc (diff) |
perf(core.js): introduce promise ring (#9979)
This is another optimization to help improve the baseline overhead
of async ops. It shaves off ~55ns/op or ~7% of the current total
async op overhead.
It achieves these gains by taking advantage of the sequential
nature of promise IDs and optimistically stores them sequentially
in a pre-allocated circular buffer and fallbacks to the promise Map
for slow to resolve promises.
Diffstat (limited to 'core/core.js')
-rw-r--r-- | core/core.js | 56 |
1 files changed, 44 insertions, 12 deletions
diff --git a/core/core.js b/core/core.js index 40a8270e3..3ec5c3e41 100644 --- a/core/core.js +++ b/core/core.js @@ -9,12 +9,53 @@ let opsCache = {}; const errorMap = {}; let nextPromiseId = 1; - const promiseTable = new Map(); + const promiseMap = new Map(); + const RING_SIZE = 4 * 1024; + const NO_PROMISE = null; // Alias to null is faster than plain nulls + const promiseRing = new Array(RING_SIZE).fill(NO_PROMISE); function init() { recv(handleAsyncMsgFromRust); } + function setPromise(promiseId) { + const idx = promiseId % RING_SIZE; + // Move old promise from ring to map + const oldPromise = promiseRing[idx]; + if (oldPromise !== NO_PROMISE) { + const oldPromiseId = promiseId - RING_SIZE; + promiseMap.set(oldPromiseId, oldPromise); + } + // Set new promise + return promiseRing[idx] = newPromise(); + } + + function getPromise(promiseId) { + // Check if out of ring bounds, fallback to map + const outOfBounds = promiseId < nextPromiseId - RING_SIZE; + if (outOfBounds) { + const promise = promiseMap.get(promiseId); + promiseMap.delete(promiseId); + return promise; + } + // Otherwise take from ring + const idx = promiseId % RING_SIZE; + const promise = promiseRing[idx]; + promiseRing[idx] = NO_PROMISE; + return promise; + } + + function newPromise() { + let resolve, reject; + const promise = new Promise((resolve_, reject_) => { + resolve = resolve_; + reject = reject_; + }); + promise.resolve = resolve; + promise.reject = reject; + return promise; + } + function ops() { // op id 0 is a special value to retrieve the map of registered ops. const newOpsCache = Object.fromEntries(send(0)); @@ -71,15 +112,7 @@ const maybeError = dispatch(opName, promiseId, args, zeroCopy); // Handle sync error (e.g: error parsing args) if (maybeError) processResponse(maybeError); - let resolve, reject; - const promise = new Promise((resolve_, reject_) => { - resolve = resolve_; - reject = reject_; - }); - promise.resolve = resolve; - promise.reject = reject; - promiseTable.set(promiseId, promise); - return promise; + return setPromise(promiseId); } function jsonOpSync(opName, args = null, zeroCopy = null) { @@ -87,8 +120,7 @@ } function opAsyncHandler(promiseId, res) { - const promise = promiseTable.get(promiseId); - promiseTable.delete(promiseId); + const promise = getPromise(promiseId); if (!isErr(res)) { promise.resolve(res); } else { |