Skip to content

Commit

Permalink
perf(core.js): introduce promise ring (denoland#9979)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
AaronO committed Apr 7, 2021
1 parent de64183 commit 2865f39
Showing 1 changed file with 44 additions and 12 deletions.
56 changes: 44 additions & 12 deletions core/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -71,24 +112,15 @@
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) {
return processResponse(dispatch(opName, null, args, zeroCopy));
}

function opAsyncHandler(promiseId, res) {
const promise = promiseTable.get(promiseId);
promiseTable.delete(promiseId);
const promise = getPromise(promiseId);
if (!isErr(res)) {
promise.resolve(res);
} else {
Expand Down

0 comments on commit 2865f39

Please sign in to comment.