From 1130aa85bea022470dca50ede780df0b7bd93129 Mon Sep 17 00:00:00 2001 From: chicoxyzzy Date: Mon, 19 Oct 2026 15:20:09 +0200 Subject: [PATCH] Add an unofficial shared-memory ring buffer workload Producer claims a slot with Atomics.add, writes the record with Atomics.store, and publishes it with Atomics.compareExchange. The consumer observes the slot with Atomics.load. Both sides run on one agent so the score does not depend on worker scheduling. The workload is not in the default suite. Run it with --test=atomics-ring-buffer. --- JetStreamDriver.js | 8 +++ atomics/benchmark.js | 145 +++++++++++++++++++++++++++++++++++++++++++ in-depth.html | 11 ++++ 3 files changed, 164 insertions(+) create mode 100644 atomics/benchmark.js diff --git a/JetStreamDriver.js b/JetStreamDriver.js index 556b4fbb..b953390e 100644 --- a/JetStreamDriver.js +++ b/JetStreamDriver.js @@ -2244,6 +2244,14 @@ let BENCHMARKS = [ worstCaseCount: 2, tags: ["default", "js", "json", "inspector", "SeaMonster"], }), + // Atomics + new DefaultBenchmark({ + name: "atomics-ring-buffer", + files: [ + "./atomics/benchmark.js", + ], + tags: ["js", "atomics"], + }), // BigInt new AsyncBenchmark({ name: "bigint-noble-bls12-381", diff --git a/atomics/benchmark.js b/atomics/benchmark.js new file mode 100644 index 00000000..52cb27d2 --- /dev/null +++ b/atomics/benchmark.js @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2026 Sergey Rubanov. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +"use strict"; + +// Shared-memory ring buffer, the producer/consumer queue behind audio graphs +// and worker-backed UI state. The producer claims a slot, writes a fixed-size +// record, and publishes a sequence number the consumer observes. Both sides +// run on this agent, so worker scheduling stays out of the score. +// https://issues.chromium.org/issues/497018236 + +const CAPACITY = 1024; +const MASK = CAPACITY - 1; +const SLOT_WORDS = 4; +const HEADER_WORDS = 2; +const MESSAGES = 32768; +const BATCH = 64; + +const WRITE_CURSOR = 0; +const READ_CURSOR = 1; + +function slotBase(seq) { + return HEADER_WORDS + (seq & MASK) * SLOT_WORDS; +} + +function payloadA(seq) { + return (Math.imul(seq, 3) + 1) >>> 0; +} + +function payloadB(seq) { + return (Math.imul(seq, 7) + 2) >>> 0; +} + +function payloadSum(seq, a, b) { + return (a + b + seq) >>> 0; +} + +function expectedChecksum(count) { + let checksum = 0; + for (let seq = 0; seq < count; seq++) { + const sum = payloadSum(seq, payloadA(seq), payloadB(seq)); + checksum = (checksum + sum) >>> 0; + } + return checksum; +} + +class RingBuffer { + constructor() { + const words = HEADER_WORDS + CAPACITY * SLOT_WORDS; + this.view = new Uint32Array(new SharedArrayBuffer(words * 4)); + this.expected = expectedChecksum(MESSAGES); + this.checksum = 0; + this.reset(); + } + + // Called from prepareForNextIteration, outside the timed region. + reset() { + this.view[WRITE_CURSOR] = 0; + this.view[READ_CURSOR] = 0; + for (let slot = 0; slot < CAPACITY; slot++) + this.view[HEADER_WORDS + slot * SLOT_WORDS] = slot; + this.checksum = 0; + } + + publish(seq) { + const base = slotBase(seq); + const sequence = Atomics.load(this.view, base); + if (sequence !== seq) throw new Error(`sequence ${sequence} != ${seq}`); + const a = payloadA(seq); + const b = payloadB(seq); + Atomics.store(this.view, base + 1, a); + Atomics.store(this.view, base + 2, b); + Atomics.store(this.view, base + 3, payloadSum(seq, a, b)); + const previous = Atomics.compareExchange(this.view, base, seq, (seq + 1) >>> 0); + if (previous !== seq) throw new Error(`publish lost sequence ${seq}`); + } + + consume(seq) { + const base = slotBase(seq); + const readCursor = Atomics.load(this.view, READ_CURSOR); + if (readCursor !== seq) throw new Error(`read cursor ${readCursor} != ${seq}`); + const sequence = Atomics.load(this.view, base); + if (sequence !== (seq + 1) >>> 0) throw new Error(`sequence ${sequence} != ${seq + 1}`); + const a = Atomics.load(this.view, base + 1); + const b = Atomics.load(this.view, base + 2); + const sum = Atomics.load(this.view, base + 3); + if (a !== payloadA(seq) || b !== payloadB(seq) || sum !== payloadSum(seq, a, b)) + throw new Error(`corrupt slot ${seq}`); + Atomics.store(this.view, base, (seq + CAPACITY) >>> 0); + Atomics.store(this.view, READ_CURSOR, (seq + 1) >>> 0); + this.checksum = (this.checksum + sum) >>> 0; + } + + run() { + for (let start = 0; start < MESSAGES; start += BATCH) { + for (let i = 0; i < BATCH; i++) { + const seq = Atomics.add(this.view, WRITE_CURSOR, 1); + this.publish(seq); + } + for (let i = 0; i < BATCH; i++) this.consume(start + i); + } + if (this.checksum !== this.expected) + throw new Error(`checksum ${this.checksum} != ${this.expected}`); + const writeCursor = Atomics.load(this.view, WRITE_CURSOR); + if (writeCursor !== MESSAGES) throw new Error(`write cursor ${writeCursor} != ${MESSAGES}`); + } +} + +class Benchmark { + constructor() { + if (typeof SharedArrayBuffer !== "function" || typeof Atomics !== "object") + throw new Error("atomics-ring-buffer requires SharedArrayBuffer and Atomics"); + this.ring = new RingBuffer(); + } + + prepareForNextIteration() { + this.ring.reset(); + } + + runIteration() { + this.ring.run(); + } +} diff --git a/in-depth.html b/in-depth.html index 149caa99..be694656 100644 --- a/in-depth.html +++ b/in-depth.html @@ -197,6 +197,17 @@

existing files. Source code: async-file-system.js +
atomics-ring-buffer
+
+ A shared-memory ring buffer, the pattern behind worker and main-thread queues + such as audio graphs and UI state shared with a worker. The producer claims a + slot with Atomics.add, writes the record with + Atomics.store, and publishes it with + Atomics.compareExchange. The consumer observes the slot with + Atomics.load. Both sides run on one agent, which keeps worker + scheduling out of the score. + Source code: atomics/benchmark.js +
babel-minify-wtb
Babel Minify is an ES2015+ aware minifier based on the