[Proxying] Use Atomics.waitAsync to receive proxying notifications - #18861
Conversation
|
Current dependencies on/for this PR:
This comment was auto-generated by Graphite. |
|
The wasm2js test failures should be addressed by WebAssembly/binaryen#5525. |
|
This dramatically improves performance for WasmFS/OPFS workloads that otherwise have low contention, even when the main thread isn't doing any extra work 🎉 🎉 🎉
(Measuring time to perform 10k fstat calls split across n threads, where each thread is working with its own separate file. Average of 10 runs with each thread count after 5 warmup runs. code) |
37d81c9 to
5573a31
Compare
f898a5d to
b04b843
Compare
5573a31 to
a2d0ef3
Compare
b04b843 to
bfa8d15
Compare
a2d0ef3 to
0fa4fc7
Compare
bfa8d15 to
d44c1f9
Compare
0fa4fc7 to
a8184bf
Compare
d44c1f9 to
1cf29d0
Compare
a8184bf to
c3b5047
Compare
1cf29d0 to
bb28e08
Compare
c3b5047 to
c30ca68
Compare
bb28e08 to
afcb63b
Compare
c30ca68 to
2f1ddd5
Compare
9b5d06e to
1cd0572
Compare
0bc125b to
c3c38e7
Compare
1cd0572 to
7b1ac4d
Compare
When it is available, register to be notified of new proxying messages using `Atomics.waitAsync` and perform the notification using `Atomics.notify`. This new notification mechanism does not depend on the main thread forwarding postMessage notifications, so its performance should not degrade when the main thread is busy. Update a test that depended on intercepting postMessage notifications to conservatively wait for 20ms instead.
7b1ac4d to
02bebfe
Compare
|
@sbc100, this is ready for a review! |
| '__emscripten_tls_init', | ||
| '_pthread_self', | ||
| 'checkMailbox', | ||
| '__emscripten_thread_mailbox_await', |
There was a problem hiding this comment.
Move this up to a few lines do it is grouped with the other __emscripten_thread_.. functions?
| @@ -1 +1 @@ | |||
| 15320 | |||
| 15563 | |||
There was a problem hiding this comment.
Presumable these losses could be more than regained if/when we stop including the old postMessage mechanism?
I guess we should add an option for that at some point? Perhaps doing it automatically on browser version?
There was a problem hiding this comment.
Right now it's possible to proxy a message to a worker before it has called Atomics.waitAsync to start listening for notifications, so we still have to use the old code path occasionally even on browsers that support Atomics.waitAsync. We could fix that in principle, but it would require waiting for the new thread to execute Atomics.waitAsync before returning from pthread_create, so it would at least moving thread lifetime management off the main thread.
|
|
||
| // Await mailbox notifications with `Atomics.waitAsync` so we can start | ||
| // using the fast `Atomics.notify` notification path. | ||
| Module['__emscripten_thread_mailbox_await'](e.data.pthread_ptr); |
There was a problem hiding this comment.
Should we just make this function always operate on the calling thread? Saved a couple of bytes here but not passing this argument?
Oh I see that this is implemented in JS which doesn't have easy direct access to the self pointer so maybe this makes sense actually.
There was a problem hiding this comment.
Yeah, this argument avoids having to call back into Wasm just to get the current pthread pointer.
| _emscripten_thread_mailbox_await__deps: ['$checkMailbox'], | ||
| _emscripten_thread_mailbox_await__sig: 'vp', | ||
| _emscripten_thread_mailbox_await: function(pthread_ptr) { | ||
| if (typeof Atomics.waitAsync === 'function') { |
There was a problem hiding this comment.
Should this be an assert instead? Are you supposed to be able to call this function at all on browsers that don't support this?
There was a problem hiding this comment.
Right now we unconditionally call this function and handle the case where Atomics.waitAsync is not supported internally in this function. Handling that case in the caller would require determining whether Atomics.waitAsync is supported from inside the Wasm module, which would either require an additional import or a new global, so this way seems simpler.
| _emscripten_thread_mailbox_await: function(pthread_ptr) { | ||
| if (typeof Atomics.waitAsync === 'function') { | ||
| // TODO: How to make this work with wasm64? | ||
| var wait = Atomics.waitAsync(HEAP32, pthread_ptr >> 2, pthread_ptr); |
There was a problem hiding this comment.
So the waiting is don't on the pthread pointer itself? (not some member?)
There was a problem hiding this comment.
Yes. It doesn't really matter what address we wait on as long as we know what its value will be when we execute Atomics.waitAsync. The first member of the pthread struct is conveniently a pointer to itself, so we know what value it will have when we wait on it.
| #if ASSERTIONS | ||
| assert(wait.async); | ||
| #endif | ||
| wait.value.then(() => checkMailbox()); |
There was a problem hiding this comment.
Can you just write wait.value.then(checkMailbox)?
There was a problem hiding this comment.
Ah yes. In a previous version of this code, checkMailbox took an optional argument, so this extra closure was necessary.
| if (pthread_ptr) { | ||
| // If we are using Atomics.waitAsync as our notification mechanism, wait for | ||
| // a notification before processing the mailbox to avoid missing any work. | ||
| __emscripten_thread_mailbox_await(pthread_ptr); |
There was a problem hiding this comment.
This seems cyclical, since emscripten_thread_mailbox_await then triggers call to checkMailbox, no?
There was a problem hiding this comment.
Yes, but only as a callback to a promise, so this doesn't cause an infinite recursion. This is how we set up for the next notification when we receive a first notification.
| pthread_self(), | ||
| emscripten_main_runtime_thread_id()); | ||
| if (thread->waiting_async) { | ||
| __builtin_wasm_memory_atomic_notify((int*)thread, -1); |
There was a problem hiding this comment.
So I guess this system doesn't actually write anything to the thread pointer here?
There was a problem hiding this comment.
I see so we keep waiting in a loop for the next thing.
There was a problem hiding this comment.
Correct. At no point does the first member of the pthread struct hold anything other than a pointer to itself.
…mscripten-core#18861) When it is available, register to be notified of new proxying messages using `Atomics.waitAsync` and perform the notification using `Atomics.notify`. This new notification mechanism does not depend on the main thread forwarding postMessage notifications, so its performance should not degrade when the main thread is busy. Update a test that depended on intercepting postMessage notifications to conservatively wait for 20ms instead.
…mscripten-core#18861) When it is available, register to be notified of new proxying messages using `Atomics.waitAsync` and perform the notification using `Atomics.notify`. This new notification mechanism does not depend on the main thread forwarding postMessage notifications, so its performance should not degrade when the main thread is busy. Update a test that depended on intercepting postMessage notifications to conservatively wait for 20ms instead.
This TODO was added in emscripten-core#18861 when the following line read: ``` Atomics.waitAsync(HEAP32, pthread_ptr >> 2, pthread_ptr); ``` I'm not sure if the TODO here was refering to that 2nd or 3rd argument but they both work fine under wasm64 right now. The second argument now uses `getHeapOffset('pthread_ptr', 'i32')` which works find under wasm64 even for addresses over 4gb. The third argument here is actually not important since its value never changes, it just needs to match the contents of the memory at the specific location. I verified that this works in wasm64_4gb mode. The `pthread_ptr` is simply converted to a U32 and then compared to the low bits of the 64-bit pthread_ptr in memory. The assert on the line below ensure that this does indeed work. If it didn't then `"not-equal"` would be returned from `Atomics.waitAsync`
This TODO was added in emscripten-core#18861 when the following line read: ``` Atomics.waitAsync(HEAP32, pthread_ptr >> 2, pthread_ptr); ``` I'm not sure if the TODO here was refering to that 2nd or 3rd argument but they both work fine under wasm64 right now. The second argument now uses `getHeapOffset('pthread_ptr', 'i32')` which works find under wasm64 even for addresses over 4gb. The third argument here is actually not important since its value never changes, it just needs to match the contents of the memory at the specific location. I verified that this works in wasm64_4gb mode. The `pthread_ptr` is simply converted to a U32 and then compared to the low bits of the 64-bit pthread_ptr in memory. The assert on the line below ensure that this does indeed work. If it didn't then `"not-equal"` would be returned from `Atomics.waitAsync`
This TODO was added in #18861 when the following line read: ``` Atomics.waitAsync(HEAP32, pthread_ptr >> 2, pthread_ptr); ``` I'm not sure if the TODO here was refering to that 2nd or 3rd argument but they both work fine under wasm64 right now. The second argument now uses `getHeapOffset('pthread_ptr', 'i32')` which works find under wasm64 even for addresses over 4gb. The third argument here is actually not important since its value never changes, it just needs to match the contents of the memory at the specific location. I verified that this works in wasm64_4gb mode. The `pthread_ptr` is simply converted to a U32 and then compared to the low bits of the 64-bit pthread_ptr in memory. The assert on the line below ensure that this does indeed work. If it didn't then `"not-equal"` would be returned from `Atomics.waitAsync` --------- Co-authored-by: Thomas Lively <tlively123@gmail.com>

When it is available, register to be notified of new proxying messages using
Atomics.waitAsyncand perform the notification usingAtomics.notify. Thisnew notification mechanism does not depend on the main thread forwarding
postMessage notifications, so its performance should not degrade when the main
thread is busy.
Update a test that depended on intercepting postMessage notifications to
conservatively wait for 20ms instead.