Use only Map for caching & add static subtrees caching #132
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This pull request modifies
htm's caching strategy in three major ways:Use only
Mapfor caching, getting rid of the previous fallback to string-keyed caching. This saves about 50 bytes in the brotli-compressed bundle.Create a separate cache for each
htm.bind(h)call, which allows us to...Add static subtree caching. In short this means that HTM tracks the staticness of subtrees it evaluates, and caches
h(...)call results for static subtrees. In short it's kind of like a on-the-fly @babel/plugin-transform-react-constant-elements (though less clever as even immutable dynamic properties/tags/chidren will prevent caching).Static subtree caching
In this context by static we mean a subtree where the root element and none of its descendants depend on dynamic values (an empty set of descendants is considered static). In the following example only the
spanelement is static. This is because theh1element depends on a dynamic child value,divdepends on a dynamic property value.main, while not directly dependent on a dynamic value, has non-static descendants (h1anddiv).The implementation takes advantage of the fact that
build(...)uses the first index of the opcode lists it created for bookkeeping. Previouslyevaluate(...)just ignored the first element, but nowevaluaterepurposes the first element to store staticness info about the subtree that the opcode list represents.The caching itself is done by rewriting the opcode list on-the-fly. Each new child that gets evaluated with
his represented in the opcode list as a slice likeCHILD_RECURSE, 0, [...omitted...]. If the evaluated childx = h(...)is determined to be static then the slice gets rewritten in-place toCHILD_APPEND, 0, x. If the opcode list is re-evaluated at some later time then thexvalue is just reused, short-circuiting the evaluation process.There are new tests to verify that subtree caching works.
Breaking changes
Due to subtree caching
his now required to be a pure function, or at least pure enough that it's OK to skip re-evaluating static subtrees. This new requirement forhcould be considered an API change.The fact that there is no a fallback for environments where
Mapis not defined is a breaking change. However even IE 11 supports Maps, and in older environments it can be polyfilled.The main interface for binding
hfunctions withhtm.bind(h)remains unchanges. This is however a trick -htm.bindis redefined by us to allow creating a new cache perhtm.bindcall. This is technically a breaking change, ashtm.call(h, ...)doesn't work like previously. As a counterpoint, the usage ofhtm.bind(h)was the documented way to use the library, whilehtm.call(h, ...)and others were not.Performance & size
The Brotli-compressed size of the library decreases by 25 bytes compared to the current master (596 B -> 571 B). The size of
htm/miniremains unchanged though.The performance downsides and benefits need more benchmarking, but here are some preliminary notes:
In the test:perf benchmark the worst-case scenario where every element is dynamic is about 4% slower compared to the current master.
Switching to a more realistic
himplementation (React.createElement) the difference disappears.Keeping the
React.createElementas thehand modifying the benchmark to the following:This PR's version is now about 42% faster than the current master.
These benchmarks do not test diffing etc. at all, only VDOM node creation.