-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
webassembly: reuse JavaScript proxy references if possible, to improve identity/equality relationships #17758
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
webassembly: reuse JavaScript proxy references if possible, to improve identity/equality relationships #17758
Conversation
@WebReflection FYI. This improved object equality as we discussed. See the tests for what improvements to expect. Note that |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #17758 +/- ##
=======================================
Coverage 98.38% 98.38%
=======================================
Files 171 171
Lines 22257 22257
=======================================
Hits 21898 21898
Misses 359 359 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
ports/webassembly/proxy_js.js
Outdated
@@ -97,13 +98,19 @@ function proxy_js_check_existing(c_ref) { | |||
|
|||
// js_obj cannot be undefined | |||
function proxy_js_add_obj(js_obj) { | |||
// See if there is an existing JsProxy reference, and use that if there is. | |||
if (proxy_js_ref_map.has(js_obj)) { |
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 know it's an ugly workaround but AFAIK there are no optimizations for has
immediately followed by get
in the JS Map world (runtimes) so that when performance is meant to be "best" and/or on the critical path, JS devs tend to abuse undefined
returned when no entry is found.
That is:
const known = proxy_js_ref_map.get(js_obj);
if (known) return known;
We're still talking O(1) with JS Map lookup for the key but while O(1) + O(1) is still O(1) in the theoretical side of BigO notation, you can skip that "O(2)" by reaching directly the value, if available, via .get(key)
.
Not a blocker, of course, but years of perf tuning makes me always use that ugly fast-path whenever it's crucial.
edit for correctness and history sake ... the suggested improvements fails when:
- a
key
in a Map is meant to be falsy or evenundefined
... for whatever reason; IIRC this would never be the case in here - if you prefer strict
known !== undefined
in thatif
statement, that's OK, the mentioned perf boost won't be affected
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.
Thanks for the review.
I was wondering if there was a more efficient way to do this, and I've now changed it to what you suggested.
Note that proxy_js_ref_map
contains integers and could potentially return 0, so we do need the known !== undefined
check.
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, that's the falsy value I was referring to ... if 0
could be returned, you definitively need that !== undefined
check 👍
06ef5d4
to
d6351ee
Compare
Code size report:
|
This option is needed for ports such as webassembly where objects are proxied and can be identical without being the same C pointer. Signed-off-by: Damien George <damien@micropython.org>
Signed-off-by: Damien George <damien@micropython.org>
This reduces memory use by reusing objects, and improves identity/equality relationships of JavaScript objects on the Python side. In 77bd8fe PyProxy's were reused when the same Python object was proxied across to JavaScript. This commit does the same thing but for JsProxy's going from JS to Python. If an existing JsProxy reference exists for the JS object about to be proxied across, then it's reused. This helps reduce the number of alive objects (memory use), and, more importantly, improves equality relationships of JavaScript objects on the Python side. Eg we now get, on the Python side: import js print(js.Object == js.Object) that prints True. Previously it was False. Note that this change does not make identity work with `is`, for example `js.Object is js.Object` is actually False. With more work that could be made True but for now we leave that as-is. The behaviour with this commit matches Pyodide semantics. Signed-off-by: Damien George <damien@micropython.org>
d6351ee
to
ffa98cb
Compare
Summary
In 77bd8fe PyProxy's were reused when the same Python object was proxied across to JavaScript.
This PR does the same thing but for JsProxy's going from JS to Python. If an existing JsProxy reference exists for the JS object about to be proxied across, then it's reused. This helps keep down the number of alive objects, and, more importantly, improves equality relationships. Eg we now get, on the Python side:
that prints
True
. Previously it wasFalse
.Testing
Tests have been added to CI. They pass locally.
Trade-offs and Alternatives
This does not make identity work with
is
, egjs.Object is js.Object
is actuallyFalse
. With more work that could be madeTrue
but for now we leave that as-is. This matches Pyodide semantics (only == is True, while is is False).This needs to maintain a separate
Map
on the JS side, of all the proxied JS objects. But that's (a) necessary and (b) worth it because of the reduced churn of JS objects when proxying them across to Python.