Performance in Halogen components

Some takeaways from another performance troubleshooting thread:

  1. Take advantage of component boundaries.
  1. Components will not re-render if new state is referentially equal to previous state.
    This feature is nice to use in child components if the input received from the parent is the entire state of the child. In the below block, the child will not re-render if the parent has not modified the input.

    While still technically true, it’s better to just use a function returning HH.HTML wrapped in HH.lazy instead of a child component.

  2. Manually prevent unnecessary state updates. (when appropriate)
    If the component’s state will be considered updated by applying new identical data, (e.g. overwriting a record field to produce a new record reference), then an unnecessary re-render will be triggered. You can prevent this re-render by manually checking for differences before updating with H.modify or H.put. Note that comparisons are different for references versus values and types with an Eq instance.

handleAction = case _ of
  HandleNewValue val -> do
    old <- H.get
    when (old.val /= val) $ H.modify_ _ { val = val }
  HandleNewReference ref -> do
    old <- H.get
    unless (unsafeRefEq old.ref ref) $ H.modify_ _ { ref = ref }
  1. Use HH.lazy and HH.memoized (when appropriate)
    Avoid re-running functions that generate HH.HTML, if their arguments are the same as before. The Elm guide and this blog post provide a good explanation of how this works. Differences between HH.lazy and HH.memoized are that lazy compares references, while memoized lets you provide a custom comparison function. lazy also supports more than one function argument (multiple args to memoized may be wrapped in a record). More details on usage are in the Halogen source.

  2. Use keyed elements for lists where items are inserted, removed, or reordered
    Again, the Elm guide describes this well. See Halogen.HTML.Elements.Keyed for the PureScript equivalent. Note that using keyed elements may result in worse performance. For example, this keyed table performs much worse than the non-keyed equivalent.

  3. Use browser debugger to check if render code is being run unnecessarily.
    It’s tough (or impossible) to detect renders in PS with logging, but you can find your component’s render code in the JS files and set a breakpoint there to catch any unnecessary re-renders.
    Edit: Debug.Trace is another option for tracking program flow.

  4. Balance cost of checking for equality against cost of re-rendering.

2 Likes