-
Notifications
You must be signed in to change notification settings - Fork 3.3k
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
Replace GROWABLE_HEAPS with native functions #18589
Comments
@kripken Just was looking at this again. My benchmark is less accurate to latest emscripten since my examples uses I think the same principal applies though. Interestingly i noticed in the new runtime there is a function function UTF8ToString(ptr, maxBytesToRead) {
return ptr ? UTF8ArrayToString(GROWABLE_HEAP_U8(), ptr, maxBytesToRead) : "";
} Isn't the whole idea area GROWABLE_HEAP_ functions is for thread safety? The case would be if the wasm memory grows the array views are stale? Doesn't that mean this function |
I'm not sure what you mean by a race condition in
Having |
I see. maybe i misunderstood to exact reason why GROWABLE_HEAP was necessary, I thought the other issue was related to the underlying buffer being relocated which could make the view's ArrayBuffer be freed. So my concern with
my thought was during those iterations buffer could be invalidated. If that isn't actually a concern then the original performance issue in It looks like: function AsciiToString(ptr) {
var str = '';
while (1) {
var ch = GROWABLE_HEAP_U8()[((ptr++)>>0)];
if (!ch) return str;
str += String.fromCharCode(ch);
}
} Where the main overhead here was the function call to GROWABLE_HEAP_U8() on each iteration of the loop which makes this comparison function AsciiToString(ptr) {
var str = '';
var heap = GROWABLE_HEAP_U8();
while (1) {
var ch = heap[((ptr++)>>0)];
if (!ch) return str;
str += String.fromCharCode(ch);
}
} With that said if there are still places invoking these "GROWABLE" functions in a "tight" loop then they could simply be pulled out of the loop. If for some reason they cannot be removed from the loop then deferring to a native function to resolve the value at the address does appear to be faster than |
Ok, then that is not a problem. If we got a pointer to a string, we can assume the string has been allocated before us. So we just need to update the view once. I would hope the JS VM will hoist such code out automatically, but if we see benefits on manually hoisting it then that can be useful. (In theory a multithreaded application could also have a bad data race in which the string is overwritten or resized, but that would be an error even without memory growth.) |
OK then we can probably close this since it appears the only code that has these issues is in the "legacy" functions. I can rerun my benchmarks if you would like to show the difference between manual hoisting and now. In my experience hoping that a VM or compiler etc will automatically fix performance problems is typically a pipe dream. Especially if you know a manual solution solves the problem |
Yeah, in this case I think you're right, I was also talking with @sbc100 offline and it makes sense this can't be hoisted - the VM can't tell that the allocation has already been done, and so since we iterate up, we might race with the growth of data. In general though I am hoping that spec and VM improvements will make this problem go away by itself. I'm not opposed to improvements in the meanwhile, though, if they are helpful. |
On possible improvement would be to update the current acorn pass such that the check is one exactly once per-js-function (or at least per-js-function-that contains a heap access), rather than on every access. It is conceivable that one could write multi-threaded app that get a new pointer from a background thread during a JS function.. but its very very unlikely anyone would do that. |
@sbc100 That seems like a reasonable idea, based on a quick scan of something built with a recent version of emscripten without the legacy function there are only a few places that do call GROWABLE_HEAP multiple times and they don't seem like something that would be overly expensive. Cleaning them up though and making sure any new ones don't turn into gotchas would be an improvement |
Would the Growable Array Buffer proposal solve this: https:/tc39/proposal-resizablearraybuffer#sync-up-capability-with-webassembly-memorygrow (coupled with the corresponding WebAssembly proposal: WebAssembly/spec#1292 ) If I understand that proposal, if the ArrayBuffers that WebAssembly memory uses can grow in place, there is no need to worry about having to refresh views in JS. |
Yes, once we have growable array buffers there would be no need for any of this. However, it might be a while before folks want to drop support for engines without growable array buffers. |
I was playing around with your benchmark, and i think i came up with an option to reduce the overhead of
warning: USE_PTHREADS + ALLOW_MEMORY_GROWTH may run non-wasm code slowly
Instead of always relying on the array views, have a native function to call into which can cast the memory address, since this is on the wasm side no need to check if the underlying buffer has changed.
Here is the new benchmark.cpp
NO_ALLOW_MEMORY_GROWTH
ALLOW_MEMORY_GROWTH
Swapping out AsciiToString with:
New Results!
The text was updated successfully, but these errors were encountered: