Skip to content

Conversation

@jviide
Copy link
Collaborator

@jviide jviide commented Aug 15, 2019

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 span element is static. This is because the h1 element depends on a dynamic child value, div depends on a dynamic property value. main, while not directly dependent on a dynamic value, has non-static descendants (h1 and div).

html`
  <main>
    <h1>${"not static"}</h1>
    <div class=${foo}></div>
    <span>totally-static</span>
  </main>
`

There are a two of breaking changes to the API:

  • The previous pattern of binding ah function, const html = htm.bind(h), is now const html = htm(h). This is due to the need to have per-h parsing caches. Previously parsing caches were global and shared between different hs.
  • h is 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. Previously evaluate(...) just ignored the first element, but now evaluate repurposes 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 h is represented in the opcode list as a slice like CHILD_RECURSE, 0, [...omitted...]. If the evaluated child x = h(...) is determined to be static then the slice gets rewritten in-place to CHILD_APPEND, 0, x. If the opcode list is re-evaluated at some later time then the x value 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:

  • 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 h implementation (React.createElement) the difference disappears.
  • Keeping the React.createElement as the h and modifying the benchmark to the following:
    html`
      <div>
        <span>${1}</span>
        <span>totally-static</span>
      </div>
    `
    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.

@jviide
Copy link
Collaborator Author

jviide commented Oct 18, 2019

Closing in favor of #132.

@jviide jviide closed this Oct 18, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant