summaryrefslogtreecommitdiff
path: root/core/core.js
diff options
context:
space:
mode:
authorAaron O'Mullan <aaron.omullan@gmail.com>2021-04-07 14:38:54 +0200
committerGitHub <noreply@github.com>2021-04-07 14:38:54 +0200
commit2865f39bec6da135a2d2d679a65e7ff139131bd7 (patch)
treeea575143149a97815db7942898074d644b095ecc /core/core.js
parentde641838831a90b142eceab5aa9df269084d83cc (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.js56
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 {