Incremental performance improvements to element creation (#3169)

* enable interning

* intern tag names

* intern attribute keys and event listener types

* intern attribute values

* cache and clone elements

* clean up the node cloning version a bit

* use HashMap instead of Vec for element cache

* Revert "intern attribute values"

This reverts commit 28653c4660dcf1942fab3b0ad7d4c840b96e0f2a.

* add `enable-interning` feature to Yew that activates the same in wasm-bindgen

* remove interning feature
This commit is contained in:
Greg Johnston 2023-04-02 15:29:21 -04:00 committed by GitHub
parent 8086a73a21
commit bdf5712d96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 36 additions and 16 deletions

View File

@ -75,7 +75,7 @@ features = [
"WheelEvent", "WheelEvent",
"Window", "Window",
"HtmlScriptElement", "HtmlScriptElement",
"SubmitEvent" "SubmitEvent",
] ]
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
@ -89,11 +89,7 @@ trybuild = "1"
[dev-dependencies.web-sys] [dev-dependencies.web-sys]
version = "0.3" version = "0.3"
features = [ features = ["ShadowRootInit", "ShadowRootMode", "HtmlButtonElement"]
"ShadowRootInit",
"ShadowRootMode",
"HtmlButtonElement"
]
[features] [features]
ssr = ["dep:html-escape", "dep:base64ct", "dep:bincode"] ssr = ["dep:html-escape", "dep:base64ct", "dep:bincode"]

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::ops::Deref; use std::ops::Deref;
use indexmap::IndexMap; use indexmap::IndexMap;
use wasm_bindgen::JsValue; use wasm_bindgen::{intern, JsValue};
use web_sys::{Element, HtmlInputElement as InputElement, HtmlTextAreaElement as TextAreaElement}; use web_sys::{Element, HtmlInputElement as InputElement, HtmlTextAreaElement as TextAreaElement};
use yew::AttrValue; use yew::AttrValue;
@ -163,9 +163,9 @@ impl Attributes {
fn set(el: &Element, key: &str, value: &str, apply_as: ApplyAttributeAs) { fn set(el: &Element, key: &str, value: &str, apply_as: ApplyAttributeAs) {
match apply_as { match apply_as {
ApplyAttributeAs::Attribute => { ApplyAttributeAs::Attribute => el
el.set_attribute(key, value).expect("invalid attribute key") .set_attribute(intern(key), value)
} .expect("invalid attribute key"),
ApplyAttributeAs::Property => { ApplyAttributeAs::Property => {
let key = JsValue::from_str(key); let key = JsValue::from_str(key);
let value = JsValue::from_str(value); let value = JsValue::from_str(value);
@ -177,7 +177,7 @@ impl Attributes {
fn remove(el: &Element, key: &str, apply_as: ApplyAttributeAs) { fn remove(el: &Element, key: &str, apply_as: ApplyAttributeAs) {
match apply_as { match apply_as {
ApplyAttributeAs::Attribute => el ApplyAttributeAs::Attribute => el
.remove_attribute(key) .remove_attribute(intern(key))
.expect("could not remove attribute"), .expect("could not remove attribute"),
ApplyAttributeAs::Property => { ApplyAttributeAs::Property => {
let key = JsValue::from_str(key); let key = JsValue::from_str(key);

View File

@ -4,6 +4,8 @@ mod attributes;
mod listeners; mod listeners;
use std::borrow::Cow; use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::hint::unreachable_unchecked; use std::hint::unreachable_unchecked;
use std::ops::DerefMut; use std::ops::DerefMut;
@ -252,9 +254,31 @@ impl VTag {
.create_element_ns(namespace, tag) .create_element_ns(namespace, tag)
.expect("can't create namespaced element for vtag") .expect("can't create namespaced element for vtag")
} else { } else {
document() thread_local! {
.create_element(tag) static CACHED_ELEMENTS: RefCell<HashMap<String, Element>> = RefCell::new(HashMap::with_capacity(32));
.expect("can't create element for vtag") }
CACHED_ELEMENTS.with(|cache| {
let mut cache = cache.borrow_mut();
let cached = cache.get(tag).map(|el| {
el.clone_node()
.expect("couldn't clone cached element")
.unchecked_into::<Element>()
});
cached.unwrap_or_else(|| {
let to_be_cached = document()
.create_element(tag)
.expect("can't create element for vtag");
cache.insert(
tag.to_string(),
to_be_cached
.clone_node()
.expect("couldn't clone node to be cached")
.unchecked_into(),
);
to_be_cached
})
})
} }
} }
} }

View File

@ -8,7 +8,7 @@ use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use wasm_bindgen::prelude::{wasm_bindgen, Closure}; use wasm_bindgen::prelude::{wasm_bindgen, Closure};
use wasm_bindgen::{JsCast, UnwrapThrowExt}; use wasm_bindgen::{intern, JsCast, UnwrapThrowExt};
use web_sys::{ use web_sys::{
AddEventListenerOptions, Element, Event, EventTarget as HtmlEventTarget, ShadowRoot, AddEventListenerOptions, Element, Event, EventTarget as HtmlEventTarget, ShadowRoot,
}; };
@ -157,7 +157,7 @@ impl EventListener {
target target
.add_event_listener_with_callback_and_add_event_listener_options( .add_event_listener_with_callback_and_add_event_listener_options(
&event_type, intern(&event_type),
callback.as_ref().unchecked_ref(), callback.as_ref().unchecked_ref(),
&options, &options,
) )