As I mentioned when
sharing draft 0.17.1 binaries, my current goals are:
- Get keyed nodes working again.
- Update the TodoMVC benchmarks to see how my rewrite of virtual-dom holds up against everything else.
I got things in a working state today, so I figured I'd share the preliminary results.
Keyed Nodes
The old API for adding keys had you add them as attributes. This was problematic for two major reasons:
- You could have lists of children where not every node had keys. This makes it very difficult for the diffing algorithm to do something sensible and efficient. Partially keyed children is also just a silly thing that seems to serve no concrete purpose.
- Lazy nodes blocked access to keys, so if all the children were lazy, it's as if the keys are invisible.
These issues were a direct result of the API inherited from the JS implementation we were using in 0.16 and before. With the reimplementation, I wanted to pick a better API and implement it better. I decided to go with the following API:
module Html.Keyed exposing (node, ul, ol)
node : String -> List (Attribute msg) -> List (String, Html msg) -> Html msg
Almost exactly the same as Html.node type, but instead of just a list of children, each child is paired with a string identifier. This ensures that every child has a key and that the children can be lazy without hiding the key.
Note: In prior discussions, I
suggested using Dict for this. That version works poorly in the following scenario: You have a list of items. They can be sorted by relevance, price, or popularity. Each item has an ID which we will use as keys. With a Dict, the order of the IDs is the order you would see on screen. Basically, it ties ordering of elements to ordering of keys which is bad.
One cool think about having a Html.Keyed module is that we can provide a few keyed nodes with nicer names. Right now I just added these two:
ul : List (Attribute msg) -> List (String, Html msg) -> Html msg
ol : List (Attribute msg) -> List (String, Html msg) -> Html msg
So you can do things like this:
import Html.Keyed as Keyed
Keyed.ul [] [ ... ]
Which feels pretty nice in the code I have been writing for benchmarks.
TodoMVC Benchmarking
I have also been working on getting the benchmarks working for 0.16 and 0.17 so I can (1) see how the key implementation is performing and (2) compare to see if the rewrite is actually faster based on the choices I made.
One of the most important things with the benchmarks was to make sure requestAnimationFrame was not being invoked. Elm is able to skip frames if they happen within the 16 ms window between each browser render. This can save a ton of work. That's nice in general, but this distracts from the core virtual-dom performance. The important question for assessing an implementation is: how long does it take to render every frame? Getting rid of requestAnimationFrame use was actually trickier than it sounds, but I figured out a way.
So I have some preliminary numbers now, and they look really great. I am seeing between 2x and 5x improvements depending on various factors, and it looks like the new key implementation is largely responsible for the 4x and above results.
I am still not 100% confident in my numbers, so for now I think it's fair to just say that things are faster. Don't put a number on it yet. I will be doing some compare and contrast with some other TodoMVC implementations to contextualize the speed gains in the broader JS ecosystem. I think that will help build more confidence in my implementation and performance numbers.
Notes
I'm not certain when I'll get a version out for people to test. I just got it working today, so it is still early. I am going to continue on the benchmarks for a bit: set up more scenarios, test different things, etc. My brain is in this world, so it makes sense to stay focused. Once that seems good, I can get the updated 0.17.1 binaries out with a way to test this stuff out on your own.