Skip to content

Instantly share code, notes, and snippets.

@pfrazee
Last active August 18, 2023 20:20
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pfrazee/a307ccd66568dac7f716391fbc9ca254 to your computer and use it in GitHub Desktop.
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
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})
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}`
}
@pfrazee
Copy link
Author

pfrazee commented Jun 17, 2020

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment