Skip to content

Commit

Permalink
feat(Notify): reimplement to improve performance
Browse files Browse the repository at this point in the history
  benchmark      time (avg)        iter/s             (min … max)       p75       p99      p995
  --------------------------------------------------------------- -----------------------------

  group Notify#notifyAll
  current       293.47 µs/iter       3,407.5 (251.46 µs … 676.88 µs) 266.04 µs 592.96 µs 608.17 µs
  v1.0.0        223.93 µs/iter       4,465.6 (200.75 µs … 452.79 µs) 215.67 µs 356.75 µs 370.08 µs

  summary
    current
    1.31x slower than v1.0.0

  group Notify#notify
  current       395.51 µs/iter       2,528.4 (344.42 µs … 953.71 µs) 365.83 µs 700.67 µs 717.75 µs
  v1.0.0         21.54 ms/iter          46.4   (20.82 ms … 23.01 ms) 21.89 ms 23.01 ms 23.01 ms

  summary
    current
    54.46x faster than v1.0.0
  • Loading branch information
lambdalisue committed Aug 16, 2024
1 parent 783063c commit 3e7f6f5
Showing 1 changed file with 16 additions and 22 deletions.
38 changes: 16 additions & 22 deletions notify.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { iter } from "@core/iterutil/iter";
import { take } from "@core/iterutil/take";

/**
* Async notifier that allows one or more "waiters" to wait for a notification.
*
Expand All @@ -23,13 +20,13 @@ import { take } from "@core/iterutil/take";
* ```
*/
export class Notify {
#waiters: Set<PromiseWithResolvers<void>> = new Set();
#waiters: PromiseWithResolvers<void>[] = [];

/**
* Returns the number of waiters that are waiting for notification.
*/
get waiterCount(): number {
return this.#waiters.size;
return this.#waiters.length;
}

/**
Expand All @@ -43,40 +40,37 @@ export class Notify {
if (n <= 0 || !Number.isSafeInteger(n)) {
throw new RangeError(`n must be a positive safe integer, got ${n}`);
}
const it = iter(this.#waiters);
for (const waiter of take(it, n)) {
waiter.resolve();
}
this.#waiters = new Set(it);
this.#waiters.splice(0, n).forEach(({ resolve }) => resolve());
}

/**
* Notifies all waiters that are waiting for notification. Resolves each of the notified waiters.
*/
notifyAll(): void {
for (const waiter of this.#waiters) {
waiter.resolve();
}
this.#waiters = new Set();
this.#waiters.forEach(({ resolve }) => resolve());
this.#waiters = [];
}

/**
* Asynchronously waits for notification. The caller's execution is suspended until
* the `notify` method is called. The method returns a Promise that resolves when the caller is notified.
* Optionally takes an AbortSignal to abort the waiting if the signal is aborted.
*/
async notified({ signal }: { signal?: AbortSignal } = {}): Promise<void> {
notified({ signal }: { signal?: AbortSignal } = {}): Promise<void> {
if (signal?.aborted) {
throw signal.reason;
return Promise.reject(signal.reason);
}
const waiter = Promise.withResolvers<void>();
const abort = () => {
this.#waiters.delete(waiter);
waiter.reject(signal!.reason);
const waiter = this.#waiters.shift();
if (waiter) {
waiter.reject(signal!.reason);
}
};
signal?.addEventListener("abort", abort, { once: true });
this.#waiters.add(waiter);
await waiter.promise;
signal?.removeEventListener("abort", abort);
const waiter = Promise.withResolvers<void>();
this.#waiters.push(waiter);
return waiter.promise.finally(() => {
signal?.removeEventListener("abort", abort);
});
}
}

0 comments on commit 3e7f6f5

Please sign in to comment.