-
Notifications
You must be signed in to change notification settings - Fork 3.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[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! |
emcc.py
Outdated
@@ -1713,6 +1713,7 @@ def setup_pthreads(target): | |||
'__emscripten_tls_init', | |||
'_pthread_self', | |||
'checkMailbox', | |||
'__emscripten_thread_mailbox_await', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -216,6 +216,10 @@ function handleMessage(e) { | |||
// Pass the thread address to wasm to store it for fast access. | |||
Module['__emscripten_thread_init'](e.data.pthread_ptr, /*isMainBrowserThread=*/0, /*isMainRuntimeThread=*/0, /*canBlock=*/1); | |||
|
|||
// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the waiting is don't on the pthread pointer itself? (not some member?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
src/library_pthread.js
Outdated
#if ASSERTIONS | ||
assert(wait.async); | ||
#endif | ||
wait.value.then(() => checkMailbox()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you just write wait.value.then(checkMailbox)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems cyclical, since emscripten_thread_mailbox_await then triggers call to checkMailbox
, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I guess this system doesn't actually write anything to the thread pointer here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see so we keep waiting in a loop for the next thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
When it is available, register to be notified of new proxying messages using
Atomics.waitAsync
and 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.