Cache static subtrees #117
Closed
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 adds 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).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).There are a two of breaking changes to the API:
hfunction,const html = htm.bind(h), is nowconst html = htm(h). This is due to the need to have per-hparsing caches. Previously parsing caches were global and shared between differenths.his now required to be a pure function, or at least pure enough that it's OK to skip re-evaluating static subtrees.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.The Brotli-compressed size of the library increases by 28 bytes compared to the current master (596 B -> 624 B). The performance downsides and benefits need more benchmarking, but here are some preliminary notes:
himplementation (React.createElement) the difference disappears.React.createElementas thehand modifying the benchmark to the following: