Elm-UI 1.1.8: Element.paragraph + Element.Lazy = Slow

Hi, so I’m currently having a very strange performance issue with my elm app. To summarize whats been a long week for me, I’m making a chat app that needs to be able to show around 300 messages. I implemented Element.Lazy so that the browser wouldn’t need to re-create the virtual nodes for each message, but eventually found out that its benefits only applied to certain Msgs. For certain events, if I clicked a button to trigger a Msg, the response would happen right away with no frame drops. Other events would cause the browser to “Recalculate Style” for several seconds and drop the frames to less than 0 fps before responding. I found out the difference was conditional visual attributes, i.e.

el
  [Bg.color <|
    if model.toggle
       then color1
       else color2]
  none

https://ellie-app.com/cYJv8QqPK3ba1

This exmaple has 3 buttons and a long list of either paragraphs or columns wrapped in lazy nodes. The first button changes its own background color and text, the second changes only its own text, and the third switches the list between paragraphs and columns.

The second button always responds quickly because it only changes some text. The first responds quickly when the long list contains columns, but slowly if its paragraphs. Just like the second button, its changes its own text, but also changes model.toggle1 which changes its own background color.

Any Msg that changes a value in the model that’s used as part of a condition for a visual attribute (background color, font size, padding, etc.) when there are paragraphs in a Element.Lazy node seem to cause those paragraphs’ virtual nodes to be re-calculated (this is my best understanding without knowing the internals of elm ui). This doesnt seem to happen with attributes like onClick, pointer, or Element.html with any Html.Attribute value (I havent gone through every possible attribute, mostly because Ive been stressing about this for a week and Im exhausted).

I tested my elm app with 1000 messages, and when I removed any uses of paragraph, the delay went from 3.5 seconds to 0.4 seconds, which is way better, but if I kept the paragraphs and instead removed any relevant conditional statements involving, usually, background colors for a specific event, the delay and frame drops would disappear for the event, but stay for others. Since the browser would still “Recalculate Style” for 0.4 seconds for events with conditional attributes, I assume the issue isn’t specific to paragraphs, but, without knowing the internals of elm ui, I can’t figure out much more.

I inspected the “text” elements using the browser devtools.

When rendered as columns, each item is two <div>s.

When rendered as paragraphs, each item is three <div>s, a <style> tag with a bunch of CSS as well as a ::before and ::after pseudo element.

Maybe that’s why it takes much longer to render the paragraphs – the browser gets much more work to do then. It has do create more nodes, and parse the same chunk of CSS a thousand times. I don’t know why elm-ui does that.

Separately, I would not recommend rendering a thousand rows of data at the same time. You can easily end up with 10 DOM nodes per row, which results in 10000 DOM nodes. My rule of thumb is to always stay below 10000 DOM nodes, in order not to slow down or crash devices, such as phones. You can use something elm-infinite-list-view 3.2.0 to avoid large numbers of DOM nodes.

3 Likes

Might this benefit from Keyed? Not sure.

Hello!

I responded in slack, but figured I’d drop the same message here in case people were curious or run into a similar problem in the future.

So, the reason this is happening is that elm-ui’s version of lazy needs to embed a stylesheet at the place where Lazy is called to be able to work with Html.Lazy. Turns out that can sometimes create large recalc steps like you’re seeing! :grimacing:

A few things to try:

  1. Maybe Lazy is unnecessary here?
  2. Avoid attaching lazy at the “leaves” and try grouping your paragraphs into sections and applying lazy to that section. That will result in fewer embedded stylesheets
  3. If you have a huge list of elements and are making changes to only one of the at a time, try using Keyed, which should help elm’s virtual dom reuse html nodes instead of rebuilding them completely. (side note to reader, they confirmed they were doing this in their app, just not in the example).

Also of note, as I’m working on elm-ui 2.0 I’m attempting to make things work without having to embed that mini stylesheet where Lazy is called. So far it looks like it’s possible! So, the hope is that in the future this won’t be an issue.

6 Likes

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.