mirror of
https://github.com/denoland/deno.git
synced 2024-11-25 15:29:32 -05:00
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.
This commit is contained in:
parent
de64183883
commit
2865f39bec
1 changed files with 44 additions and 12 deletions
56
core/core.js
56
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 {
|
||||
|
|
Loading…
Reference in a new issue