Skip to content
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

Merged
merged 4 commits into from
Mar 8, 2023

Conversation

tlively
Copy link
Member

@tlively tlively commented Feb 27, 2023

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.

@tlively
Copy link
Member Author

tlively commented Feb 27, 2023

The wasm2js test failures should be addressed by WebAssembly/binaryen#5525.

@tlively
Copy link
Member Author

tlively commented Feb 27, 2023

This dramatically improves performance for WasmFS/OPFS workloads that otherwise have low contention, even when the main thread isn't doing any extra work 🎉 🎉 🎉

Threads OPFS w/ waitAsync (ms) OPFS w/ postMessage (ms) Ratio
1 320.7 508.8 0.63
2 157.2 304.5 0.52
4 142.6 169.4 0.84
8 123 129.1 0.95
16 141.5 148.7 0.95

(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)

@tlively tlively force-pushed the proxying-atomics-wait-async branch from f898a5d to b04b843 Compare February 28, 2023 15:39
@tlively tlively force-pushed the proxying-promise branch from 5573a31 to a2d0ef3 Compare March 1, 2023 01:10
@tlively tlively force-pushed the proxying-atomics-wait-async branch from b04b843 to bfa8d15 Compare March 1, 2023 01:10
@tlively tlively force-pushed the proxying-promise branch from a2d0ef3 to 0fa4fc7 Compare March 1, 2023 16:38
@tlively tlively force-pushed the proxying-atomics-wait-async branch from bfa8d15 to d44c1f9 Compare March 1, 2023 16:38
@tlively tlively force-pushed the proxying-promise branch from 0fa4fc7 to a8184bf Compare March 1, 2023 19:37
@tlively tlively force-pushed the proxying-atomics-wait-async branch from d44c1f9 to 1cf29d0 Compare March 1, 2023 19:37
@tlively tlively force-pushed the proxying-promise branch from a8184bf to c3b5047 Compare March 1, 2023 21:07
@tlively tlively force-pushed the proxying-atomics-wait-async branch from 1cf29d0 to bb28e08 Compare March 1, 2023 21:07
@tlively tlively force-pushed the proxying-promise branch from c3b5047 to c30ca68 Compare March 1, 2023 22:33
@tlively tlively force-pushed the proxying-atomics-wait-async branch from bb28e08 to afcb63b Compare March 1, 2023 22:33
@tlively tlively force-pushed the proxying-promise branch from c30ca68 to 2f1ddd5 Compare March 2, 2023 02:21
@tlively tlively force-pushed the proxying-atomics-wait-async branch 2 times, most recently from 9b5d06e to 1cd0572 Compare March 4, 2023 00:27
@tlively tlively force-pushed the proxying-promise branch from 0bc125b to c3c38e7 Compare March 6, 2023 16:46
@tlively tlively force-pushed the proxying-atomics-wait-async branch from 1cd0572 to 7b1ac4d Compare March 6, 2023 16:46
Base automatically changed from proxying-promise to main March 6, 2023 18:09
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.
@tlively tlively force-pushed the proxying-atomics-wait-async branch from 7b1ac4d to 02bebfe Compare March 6, 2023 19:53
@tlively
Copy link
Member Author

tlively commented Mar 6, 2023

@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',
Copy link
Collaborator

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
Copy link
Collaborator

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?

Copy link
Member Author

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);
Copy link
Collaborator

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.

Copy link
Member Author

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') {
Copy link
Collaborator

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?

Copy link
Member Author

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);
Copy link
Collaborator

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?)

Copy link
Member Author

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.

#if ASSERTIONS
assert(wait.async);
#endif
wait.value.then(() => checkMailbox());
Copy link
Collaborator

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)?

Copy link
Member Author

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);
Copy link
Collaborator

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?

Copy link
Member Author

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);
Copy link
Collaborator

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?

Copy link
Collaborator

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.

Copy link
Member Author

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.

@tlively tlively enabled auto-merge (squash) March 7, 2023 23:20
@tlively tlively merged commit db28eec into main Mar 8, 2023
@tlively tlively deleted the proxying-atomics-wait-async branch March 8, 2023 01:19
impact-maker pushed a commit to impact-maker/emscripten that referenced this pull request Mar 17, 2023
…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.
impact-maker pushed a commit to impact-maker/emscripten that referenced this pull request Mar 17, 2023
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants