-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Investigate caching of static strings #1386
Comments
I specialised I wonder how many more cases are affected by these hidden costs. |
Thanks for the report! Could you expand a bit on how |
I was surprised about that connection showing up in profile as well, but from looking at the source it might be related to Note that I'm mostly iterating over small tuple-like arrays (but need this code in a generic context to accept arbitrary iterables), so this overhead could well be the reason. |
Oh I think that's fixable with a change like: diff --git a/crates/js-sys/src/lib.rs b/crates/js-sys/src/lib.rs
index 0a51ec7d..05a5a656 100644
--- a/crates/js-sys/src/lib.rs
+++ b/crates/js-sys/src/lib.rs
@@ -1107,14 +1107,20 @@ pub fn try_iter(val: &JsValue) -> Result<Option<IntoIter>, JsValue> {
return Ok(None);
}
+ #[wasm_bindgen]
+ extern "C" {
+ type MaybeIterator;
+ #[wasm_bindgen(getter, method)]
+ fn next(this: &MaybeIterator) -> JsValue;
+ }
+
let iter_fn: Function = iter_fn.unchecked_into();
let it = iter_fn.call0(val)?;
if !it.is_object() {
return Ok(None);
}
- let next = JsValue::from("next");
- let next = Reflect::get(&it, &next)?;
+ let next = it.unchecked_ref::<MaybeIterator>().next();
Ok(if next.is_function() { Want to test that out and see if it works for you? |
Haha, that's literally what I did locally too, even the name of But, as mentioned above, I'm rather interested in a more generic solution to needless copies of constant strings, and decided to open this issue for that discussion/tracking. |
~~UPD: UPD: Ah no, sorry, I just didn't add |
This allows to significantly speed up iteration over small collections, where string encoding is the primary overhead. Related to rustwasm#1386, but works around only this partial case.
I think it is worth providing string interning infrastructure. This shouldn't require anything special from Therefore, the question is raised of whether it should be
I'm not convinced that we need to do the interning at compile time with proc-macros; I think we can dynamically intern at runtime via |
I tried several approaches so far:
|
I think I agree with @fitzgen here that this is best done in a separate crate for now rather than in |
Hmm, this can be hard to do in a separate crate in a way suggested above, because it needs to rewrite WASM itself in wasm-bindgen compatible manner... But this is not an easy or short-term task so I guess it's fine to have it closed. My only beef with closing long-standing issues is that it usually increases likelihood of someone not finding an open issue and raising exactly same suggestion. |
@alexcrichton Do you think this issue could be reopened in light of these details? I can still work on a PR, but it's usually much better to be able to link to an existing issue. |
Unless we have compelling data we should do this I'd rather not accumulate a large list of "wish to haves in the far and distant future" issues if we can. |
I've been investigating (and improving) the performance of dominator, and I ran into this issue. It turns out, strings are really slow. They are orders of magnitude slower than everything else. They are slow because of all the encoding and copying, and in addition also because it adds a lot of extra garbage collection pressure. The end result is that right now, Rust apps are doomed to be slower than JS apps, simply because JS apps avoid the huge costs of encoding + GC. Unfortunately, strings are unavoidable, because so many native web APIs use strings. And WebIDL probably won't help much (at least not in the short term). I re-implemented over a dozen of the web-sys bindings so that they accept We can do a lot better than that, though. Nobody should need to re-implement all of web-sys just to get acceptable string performance. It gets worse when you consider that libraries like gloo use web-sys, and therefore I had to re-implement gloo as well. I think we should do all of @RReverser 's ideas: have very fast automatic static caching for That will give very good performance for static strings, good performance for non-static strings, and predictable performance for manually interned strings. wasm-bindgen should be able to do the caching automatically, it just has to add in a call to the intern function for I'm willing to make a PR for this, if the idea is accepted. Or if you prefer we can go through the RFC process (I can write the RFC). |
That all I think is some very compelling data and I'd be inclined to agree! I think one alternative is to add bindings for both One thing I'd ideally like to see is a global cache that's tunable and optional, so we might not have it on by default for the |
That was going to be my first suggestion, but I decided against it since as you said it's untenable: gloo would need to provide The other thing I thought about was to create a new trait, something like However, that won't work for
I absolutely agree.
Great, I'll get started on that, then! |
Oh I have't noticed that @Pauan has since implemented this in #1612, this is pretty cool! One nitpick is that there doesn't seem to be any scope for interned strings and no way to free them once they're no longer necessary? My usecase is any Wasm module that creates classes. In this case it needs to intern strings that are not alive for the entire duration of the module, but rather will be unnecessary as soon as class is freed from the JS side. If my understanding is correct, these currently would never get evicted, but rather keep leaking memory for each class instance. Would it be worth to expose |
I don't like the idea of exposing internal details, but I definitely agree that we should have an API for clearing out items from the cache. It was omitted from the initial implementation to keep things as simple as possible, but it was always planned to add it later. |
@RReverser PR is up: #1828 |
Motivation
Long ago I noticed that currently even constant strings are routed through entire encoding/decoding process on each pass between Rust and JS, becoming unnecessarily expensive part of the resulting code.
Now I've noticed that this affects even internal functions which need intermediate strings, thus affecting even more potentially hot paths.
E.g. this is a profile from my WASM library that heavily relies on JS iteration -
js_sys::try_iter
- which, in turn, recreated same constant string for the iteration protocol over and over.We can fix this particular function but, ideally, we should have a more generic solution for constant strings.
Proposed Solution
Either:
JsValue::from_str
with pointers to a data section and statically extract these into JS.lazy_static!
(but then we need to somehow detect whether a string is in the data section, which is a bit harder in runtime but not impossible).js_const_str!("...")
that would extract these statically like (1), although could make it easier to detect by being more expicit.All of the proposed solutions require choosing the right balance between complexity of the solution itself vs complexity of usage from API point of view (we don't want to add a function that most users would forget to use).
The text was updated successfully, but these errors were encountered: