voby: Sierpinski triangle demo is missing per-node expensive computation

Hello, just a thought about the rendering performance demo:

https://codesandbox.io/s/voby-demo-triangle-l837v0?file=/src/index.tsx:222-385

const useSeconds = (): Observable<number> => {

  const seconds = $(0);

  useInterval ( () => seconds ( ( seconds () % 9 ) + 1 ), 1000 );

  return seconds;
};

I think that useSeconds() should be “wrapped” into an intermediary observable that triggers at the same time as the seconds signal which is scheduled to reliably fire at every seconds tick. The goal of this “wrapper” observable is to introduce a computationally-taxing operation, per node of the triangular structure (just as in the original React demo).

This is specifically what makes this stress-test relevant, as remarked by Ryan Carniato:

https://github.com/ryansolid/solid-sierpinski-triangle-demo#solid-sierpinski-triangle-demo

See:

https://github.com/ryansolid/solid-sierpinski-triangle-demo/blob/889db98d7697bd39d8fe3ca0e0134fb2788b4447/src/main.jsx#L46-L51

    var e = performance.now() + 0.8;
    // Artificially long execution time.
    while (performance.now() < e) {}
};

(sorry, I don’t know enough about Voby to contribute a PR)

I used the same technique in my stress-test for fine-grain reactivity / observable lib experiment (I use uHTML for DOM rendering). Otherwise, rendering at 200 FPS is way too easy 😃

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 19 (10 by maintainers)

Most upvoted comments

Going back to the “Sierpinski triangle” (this issue) 😅 …

This really is a useful stress test … when implemented correctly!

Normally with these kinds of “graphical” benchmarks (like your SVG clock 😃 ), I observe 200 FPS (5ms time budget per rendering frame) when the frame rate is not artificially capped by requestAnimationFrame() (i.e. 60 FPS / 16ms per frame, on a laptop display).

That’s with uHTML doing a full top-down render pass at every frame, which (if I understand correctly) involves a cache of DOM “expressions” when processing tagged template literals, and a highly-optimised diff implementation.

The “triangles” demo adds an additional layer of stress-testing over mere DOM rendering, useful for asserting CPU and memory efficiency in a reactivity lib. I observe between 130-150 FPS under stress, instead of the usual 200 FPS. Note that visual stuttering can be caused by rendering frames not aligning perfectly with the optimal requestAnimationFrame() … but in my tests on Firefox, Chrome and Safari, dropped frames were usually caused by inefficient update propagation in the reactive graph. This extreme use-case therefore helped me fine-tune my implementation.

In a nutshell, there are 3 sources of stress in this demo:

  1. There is a single top-level observable number that records the current timestamp at each frame. This is used to calculate the effective rendering frame rate, in real-time (this also causes a render refresh to cycle the overall 2D scaling, which is a DOM mutation, not just a CSS property/var update). This primitive reactive unit fires at a high rate, but it is located at the root of the dependency tree. In this “triangles” demo, there is in fact only one computed / derived observable (i.e. the “root” itself), so note that this is a very limited test-case for a reactivity / DAG graph algorithm. In the SolidJS benchmark suite, this shallow tree is equivalent to instantiating many non-nested / flat children dependencies.
  2. There are 729 “blobs” inside the recursive triangle structure. Each blob has its own observable primitive unit for mouse over (boolean on/off), but once again the layout is very shallow. The key aspect here is that the user can introduce additional stress by hovering the mouse rapidly over blobs, thereby triggering DOM mutations in quick successions (“layered” over the fast-refresh caused by (1), so depending on how renders are scheduled by the underlying DOM/VDOM engine, this might cause visible stuttering).
  3. There is a single observable which “ticks” every second to update the number rendered inside each “blob” (also a DOM mutation). This tick event also introduces artificial CPU load by running a no-op loop.

It is the cumulated effect of all 3 points that may cause visible slow-down / stuttering with some frontend “implementations” (by which I mean the combination of a rendering lib / scheduling technique + “state management” code).

Codesanbox demo link: https://cnmehi.csb.app

Codesanbox IDE link: https://codesandbox.io/s/uhtml-obs-demo-triangle-forked-cnmehi?file=/public/obs.js