[HN Gopher] Fixing a performance problem in Elm using Html.Lazy
___________________________________________________________________
Fixing a performance problem in Elm using Html.Lazy
Author : ingve
Score : 64 points
Date : 2021-12-13 13:37 UTC (9 hours ago)
(HTM) web link (blogg.bekk.no)
(TXT) w3m dump (blogg.bekk.no)
| matsemann wrote:
| It's a bit pain to update nested types in elm, that's why I've
| often ended up having data that can change as flat as possible.
| But that also means that a too big a model is passed around
| between functions I guess, because it's annoying having to
| extract and pass the 3-4 values it needs.
|
| What would happen if the view functions in elm by default were
| lazy? Since everything in elm is immutable, and the functions
| can't have side effects, it shouldn't be able to break anything.
| I guess the problem is that this kind of memoization can be
| memory heavy? But it doesn't really have to memorize everything.
| Just the last inputs, and then do nothing with the DOM if the
| inputs are the same. And the inputs are all derived from the main
| model, which is only ever updated one place in the runtime. So
| just keep track of changes when that happens, and what functions
| that affect, hmm.
| yakshaving_jgt wrote:
| I've found lenses to be a reasonable way around this issue, so
| I'm not too uncomfortable with nesting records. If you're
| interested, I wrote a bit about that here[0].
|
| [0]: https://jezenthomas.com/how-i-write-elm-applications/
| _greim_ wrote:
| > What would happen if the view functions in elm by default
| were lazy?
|
| `lazy` incurs a performance penalty not in memory but in
| execution steps, due to having to do a deep-equality comparison
| on the prev/next view inputs in the underlying JS. Enabling
| such a check globally would, on balance, slow everything down.
| Only if the work of running the view exceeds that small
| penalty, would you want to use it strategically here and there,
| hence the reason it's opt-in.
|
| Even if the Records and Tuples proposal[1] lands and Elm gets
| refactored to use it internally, thus allowing `===`
| comparisons in the underlying JS instead of deep-equality, it's
| unclear whether enabling it globally would be good or bad,
| simply because the JS engine itself would presumably still be
| doing some kind of deep-equality check behind that innocuous-
| looking `===` operator. When the time comes I'll be curious to
| see if there's any progress on that front.
|
| [1] https://github.com/tc39/proposal-record-tuple
| Skinney wrote:
| It's a shallow comparison, not deep.
|
| But yes, there is both computational and memory overhead when
| using Html.Lazy
| matsemann wrote:
| But isn't there also that when _not_ using lazy? Instead of
| checking model equality, elm runtime now has to check
| shadow dom equality and calculate dom operations to apply.
| My thought is that my idea isn 't obviously worse.
| Skinney wrote:
| Yes, but lazy adds to that work. So for views that will
| pretty much always need to be re-rendered (because the
| only changes are at the bottom of the view hierarchy)
| then wrapping them in lazy will only slow down the dom
| recalculation.
| _greim_ wrote:
| > It's a shallow comparison, not deep.
|
| Interesting. I thought it was doing a deep comparison, but
| apparently it uses reference equality, and thus shallow.
| Kaze404 wrote:
| > a too big a model is passed around between functions I guess,
| because it's annoying having to extract and pass the 3-4 values
| it needs
|
| You can always take an extensible record type instead of the
| entire model. type alias Model = {
| count : Int , prop : String} initialModel
| : Model initialModel = { count = 0 ,
| prop = "" } foo : { a | prop: String } -> String
| foo model = model.prop bar = foo initialModel
| vijaybritto wrote:
| From what I understand this is the same as memo() in React? Also
| the splitting up models to only send data that is needed for the
| view sounds like matchStateToProps when using Redux. Funny how
| the problems in browsers make us solve the same problems in
| different ways and we end up feeling mind blown every time!!
| truculent wrote:
| Nice post! I enjoyed it.
|
| I don't understand why Html.Lazy uses referential equality
| instead of equality. In a language like Elm shouldn't they be the
| same? What potential bugs would using equality introduce?
|
| Thanks
| masklinn wrote:
| From what I understand it's Html _.Styled_.Lazy (from elm-css)
| which uses referential equality, possibly because it defines
| its own vdom in order to integrate styling information.
| truculent wrote:
| I think it is both.
|
| In the process of confirming, I think I found the answer
| within the Elm docs[0]:
|
| > Note: When are two values "the same" though? To optimize
| for performance, we use JavaScript's === operator behind the
| scenes
|
| > ...
|
| > Using reference equality is always cheap O(1), even when
| the data structure has thousands or millions of entries. So
| this is mostly about making sure that using lazy will never
| slow your code down a bunch by accident. All the checks are
| super cheap!
|
| It's not as elegant but you'd only ever use Html.Lazy for
| performance, so this trade-off seems to make perfect sense.
|
| [0]: https://guide.elm-lang.org/optimization/lazy.html
| Skinney wrote:
| Author here. It's both. Html.Styled is converted to regular
| Html when run, meaning it uses <<normal>> Html.Lazy under the
| hood.
___________________________________________________________________
(page generated 2021-12-13 23:01 UTC)