Last active
August 18, 2023 20:20
-
-
Save pfrazee/a307ccd66568dac7f716391fbc9ca254 to your computer and use it in GitHub Desktop.
Using SharedArrayBuffer and Atomics to do synchronous messaging from a Web Worker to the main thread
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const RES_SIZE = 256 | |
const encoder = new TextEncoder('utf8') | |
var myWorker = new Worker('/worker.js') | |
console.log(myWorker) | |
const exportedMethods = { | |
hello (str) { | |
return `hello ${str}` | |
} | |
} | |
const sharedCtrlBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT) | |
const ctrlBuffer = new Int32Array(sharedCtrlBuffer) | |
const sharedValueBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * RES_SIZE) | |
const valueBuffer = new Int32Array(sharedValueBuffer) | |
const encodeBuffer = new Uint8Array(RES_SIZE) // TextEncoder cant use SharedArrayBuffers | |
function ctrlSignal (value) { | |
Atomics.store(ctrlBuffer, 0, value) | |
Atomics.notify(ctrlBuffer, 0) | |
} | |
myWorker.onmessage = (e) => { | |
// call method | |
var [methodName, ...methodArgs] = e.data | |
// console.debug('calling', methodName, methodArgs) | |
var res = exportedMethods[methodName](...methodArgs) | |
// write response | |
// console.debug('writing response') | |
var resJson = JSON.stringify(res) | |
encoder.encodeInto(resJson, encodeBuffer) | |
// valueBuffer.set(encoder, 0) | |
for (let i = 0; i < resJson.length; i++) { | |
Atomics.store(valueBuffer, i, encodeBuffer[i]) | |
} | |
// notify success and chunk count | |
// console.debug('notifying response, size:', resJson.length) | |
ctrlSignal(resJson.length) | |
} | |
myWorker.postMessage({sharedCtrlBuffer, sharedValueBuffer}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const RES_SIZE = 256 | |
const decoder = new TextDecoder('utf8') | |
var ctrlBuffer | |
var valueBuffer | |
var decodeBuffer = new Uint8Array(RES_SIZE) // TextDecoder cant use SharedArrayBuffers | |
self.addEventListener('message', e => { | |
console.debug('worker started') | |
const {sharedCtrlBuffer, sharedValueBuffer} = e.data | |
ctrlBuffer = new Int32Array(sharedCtrlBuffer) | |
valueBuffer = new Int32Array(sharedValueBuffer) | |
main() | |
}, false) | |
function ctrlWait () { | |
Atomics.store(ctrlBuffer, 0, 0) | |
Atomics.wait(ctrlBuffer, 0, 0) | |
return ctrlBuffer[0] | |
} | |
function syncCall (...args) { | |
// send call | |
self.postMessage(args) | |
// wait | |
var resSize = ctrlWait() | |
// console.debug('response size:', resSize) | |
// read response | |
for (let i = 0; i < resSize; i++) { | |
decodeBuffer[i] = Atomics.load(valueBuffer, i) | |
} | |
// decodeBuffer.set(valueBuffer) | |
var res = decoder.decode(decodeBuffer.slice(0, resSize)) | |
// console.debug('res received', res, res.length) | |
return JSON.parse(res) | |
} | |
function main () { | |
// test | |
// | |
console.log(syncCall('hello', 'world')) | |
// benchmark | |
// | |
console.time('local') | |
var r1 = [] | |
for (let i = 0; i < 1000; i++) { | |
r1.push(hello(i)) // this will get inlined so it's not really a great benchmark | |
} | |
console.timeEnd('local') | |
console.time('remote') | |
var r2 = [] | |
for (let i = 0; i < 1000; i++) { | |
r2.push(syncCall('hello', i)) | |
} | |
console.timeEnd('remote') | |
} | |
function hello (str) { | |
return `hello ${str}` | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I'm still a rookie with the JS TypedArray APIs and I'm pretty sure my encode/decode buffers are the wrong sizes (1/4 the shared buffer size? not sure)