React in concurrent mode: 2000 state-connected comps re-rendered at 60FPS
twitter.com
twitter.com
Here's Unity maintaining a smooth framerate while updating three times that many cubes: https://www.youtube.com/watch?v=qVMfKJfsHQg
Unlike the React demo, all of the cubes actually move each frame (it's not "rescheduling" updates to later frames like React Concurrent Mode does), and it's doing a complex physics simulation to decide where the cubes should go.
This shot from Super Mario Galaxy simulates around 3,000 particles, on top of all the other bone/joint animations that are happening. Performance like this is possible in a web browser, but you wouldn't think so given how popular React and ThreeJS are.
https://noclip.website/#smg/AstroGalaxy;AAI4t49Qk^u9Ld&YUm,m...
Updating the state of thousands of DOM nodes at a steady 16ms per frame in a browser is hard enough that you have to go beyond "just update them all every frame" like in a simple particle system. To go fast you have to move to working out what needs to update based on what changed in the state.
Most web app developers don't want to have to think too hard about how it gets on the screen. They, very reasonably in my opinion, want to work on the app logic itself instead. Unfortunately that's how we end up with janky UIs. If React's concurrent mode can just be fast by default by spreading out updates if the framerate is falling that's good for everyone.
No, it's really not. I've done it before, and profiled it. 16ms is a super long time to a computer. You should easily be able to update tens, if not hundreds of thousands of DOM nodes, assuming you aren't doing anything weird like blocking on layout in one of them.
Let's aim for frameworks that are in the ballpark of peak raw performance, before we look to parallelism and multi-threading as a solution.
That someone possibly had the mistaken belief that this was peak performance for a hand-rolled WebGL codebase shows how low and out-of-touch our expectations for performance really are.
I totally agree on this.
> assuming you aren't doing anything weird like blocking on layout in one of them.
An assumption that react cannot make.
React's core promise is to make app developers lives easy, not to be performant. Performance is atrocious on the web in general, yet here it is as (one of?) the most dominant app development platforms.
With that easiness what you get is plenty of horrible (from a performance perspective) applications that get their jobs done. Some even very elegantly on many other metrics.
> Let's aim for frameworks that are in the ballpark of peak raw performance
That is a nice aim, but performance can be traded for other things. Immutable data structures will never be as performant as mutable ones, yet plenty of people make the conscious decision of using the former.
I see React's approach similary. I've built plenty of web applications in my life, and very rarely did (non-network) performance become a significant issue. As you said, computers are so powerful nowadays that most people (even devs!) cannot even comprehend it at an intuitive level.
It is always the app's logic, changing business requirements, etc. that bite me in the ass. I want tools that make that part easier. React is definitely one of those tools today. And if they found a way to keep the easiness while being better performance-wise then... yay!
Yeah, and still a good number of electrons apps arrive to put my CPU at 100% for something as advance as "text editing".
CPU are powerful when used right.
No one thought that though. This is a demo of good performance using a web framework on top of a WebGL framework. It's showing that a future version of React will make building a solid 60fps web app UI is within the reach of most web developers. Sure, you can hand-roll code to get that performance today if you know how, but this is about putting that performance in the hands of developers who can't (or, more often, aren't given the resources to). To argue that is unnecessary or actually bad is ridiculous. Libraries that make it easier to build better apps are universally good things.
> I'm pretty sure it will run circles around even the best performing manual three or webGL apps out there soon.
Last I tried that was back in 2013. Haven't tried that particular benchmark since, but have tried others, and I don't believe things have changed much to move that particular needle.
The benchmark was updating the translate3d(random, random, 1px) of multiple 20x20 divs with a black background. It would increase the number of divs until we'd start to have dropped frames.
Obviously no re-layout, no repaint, practically no time spent on the script, and no GC. Compositor only.
Chrome on Windows on a beefed up PC maxed out at around 400 squares (divs).
So no, hundreds of thousands of divs would not be possible. Not on Blink's architecture at least. WebRender is trying to change that, but it's still too early to tell.
I tried the same benchmark with hand-written WebGL, and made sure that the black squares are drawn one-by-one to simulate how Chrome did it (no batched draw calls).
That maxed out at around 1k squares. Obviously all that time was being wasted back-and-forthing between the script and the GPU.
Then tried batching the draw calls (square coords on a large typed array), and that got me to, iirc, millions.
> That someone possibly had the mistaken belief that this was peak performance for a hand-rolled WebGL codebase shows how low and out-of-touch our expectations for performance really are.
True. It is mathematically impossible for react to beat hand-optimized WebGL. I'd guess it won't come close to 1/100 of the raw throughput. It would require a different programming model. And then it would no longer be React.
Two things produce conviction in people regarding technical matters: ignorance and experience. You clearly have the experience.
Tech needs more people like you.
Here's a dead simple demo in pure vanilla JavaScript: https://brianbeck.com/particles.html
Go ahead and see what number you get to before it drops below 60fps. Personally I have to decrease it to 1000 or so on my 2018 MacBook Pro.
And that's updating a style property that does not cause reflow calculation.
Anyone including Jasper_ is welcome to post a similar demo of their 100x+ performance improvements on this, but I doubt they will.
I will admit that DOM is some serious black magic, the browser is an unfortunate moving target, and when I say "should be able to update 10k", I am lying as much blame at the feet of the browser developers as I am at the frameworks.
I would go back and edit my old post now, since you're clearly right in this case, but it seems the time limit on it has run out.
[0] https://github.com/WICG/display-locking/blob/master/README.m...
For comparison, my quick-and-dirty React version of the demo spends around 100 ms in React/updating DOM elements when you have 50,000 particles: https://jsfiddle.net/t6js8k5e/ (If you try that one at home, be warned that the browser might hang for a few seconds when you click the "Animate" button.)
I'm certainly not a web performance expert by any means, but this seems to support Jasper_'s assertion that you can make tens of thousands of DOM updates in 16 ms and that frameworks add substantial overhead to this.
(And FWIW, my modified non-React version of the demo maintains 60fps with 2,000 particles in Safari on my MacBook Pro from 2015. Admittedly, that's without many other programs/tabs running.)
This was my point actually, not that React adds no overhead which of course it does.
Updating a property on a bunch of DOM nodes, without actually waiting for their effects to be applied by the browser, is of course very fast. But the browser code (reflow, paint, etc.) is part of the frame budget, and the number of nodes you can change – accounting for those changes actually appearing on screen – within that budget is embarrassingly small regardless of whether you are doing raw DOM operations vs. using a framework like React.
It doesn't really count to say "ah yes, but the code that technically made the update was fast!" when the updates haven't actually been committed to the screen yet.
Are you kidding? Look at any modern 3D video game on any platform, INCLUDING WebGL.
> You should easily be able to update tens, if not hundreds of thousands of DOM nodes
We're talking about updating multiple DOM nodes dude, not a 3D rendering engine or a single canvas element. People don't make React websites where everything is drawn in canvas.
(See Jasper’s later comment that the described scenario was in fact a very specific example that probably doesn’t extend to many very common simple DOM manipulations and might have relied on a browser-specific optimization that no longer exists.)
Very cool website! I just noticed you're the one behind it[1]! Just curious: when people contribute textures toy our project, do they have to get permission from the video game publisher first?
Also, how hard is the process of extracting the textures of a video game? Is there effort involved in stitching multiple DDS/other texture files together, so that it can presentable on your website?
In the case of the Galaxy levels, it's reading the raw files as shipped on the Wii disc. I have my own renderer and emulation tech [0]. It's the only way to ensure accuracy to a degree I'm happy with. For textures specifically, they're stored in the Wii's texture formats, so I have my own texture decoder [1].
I did not get permission from the original game developers, but I think I'm doing everything here in good faith, out of love for the source material, and that I'm not replacing the experience of playing the game for yourself. The developers of Fez specifically retweeted a post about it on the official Twitter.
[0] https://github.com/magcius/noclip.website/blob/master/src/gx... [1] https://github.com/magcius/noclip.website/blob/master/src/as...
There are some stuttering frames every 300k objects or so, I guess this is when WebGL needs to grow buffers under the hood. Hoping for WebGPU to fix those problems.
https://floooh.github.io/oryol/wasm/Instancing.html
No matter how you look at it, moving a few thousand things around on screen really isn't all that impressive, you just have to know the weaknesses of the platform (WebGL's is a high draw call overhead) and work around them.
Matrix multiplication should not have to be scheduled!
Whereas a game engine is much more about raw speed, and every element is updated on each tick.
I would be surprised if the overhead of React and concurrent mode bookkeeping were worth it for a game engine.
Also, I don't know if people want an e-shopping experience that has the visual complexity of feeling like they're playing a video game.
This could explain the unneccessary complexity of the vdom for normal websites and apps. In VR you need high staedy framerates, the vdom could be used to shorten the round trip from client to server, as some kind of framebuffer.
React VR is a thing, though.
That type of rendering is more about GPU load than of anything React (DOM) related.
As of DOM rendering of comparable number of nodes then this:
https://terrainformatica.com/2019/07/29/bloomberg-terminal-h...
900 elements re-rendered on each frame of kinematic scroll (60 FPS) with 10% CPU load. And 250 FPS max on typical high-DPI monitor.
In other tests underlying recordset is updated with 25 FPS frequency. The view observes and updates screen for visible rows - 2% CPU for that.
All that in main GUI thread so I do not understand the excitement.
A naive minesweeper implementation (that is state-connected) will get upset at a hundred cells, and terribly laggy at 900 (a standard 30x30 super expert board).
This means devs have to abandon reactive programming for parts of their code with a non trivial component count.
The demo is showing 'smooth' perf on these kinds of "state-connected" workloads.
The graphics side of things is a partial distraction, mainly it will mean web devs won't have to make the current performance vs ease of development tradeoff in webapps (think boring saas stuff).
The way in which it isn't a distraction is it will make it possible to get past CSS limitations inside of react in a very intuitive way.
As this raises the low bar react had for game perf, we will no doubt see more react games.
But have no doubt - this is about what the current state of reactive programming is capable of off the shelf.
What are they doing?!
Does that mean if you try to update too many things it will simply... not update them in order to maintain 60fps? That does not seem ideal.
In this particular example, a bunch of random objects updated every frame, will that result in a "spiral of death"? Meaning every frame M transform requests are made. But it will only process N (where N < M) requests. Or does it drop parts of the queue if it didn't process a transform before receiving a second update for the same transform?
Is updating 2000 boxes per second really considered an "impossible" amount to update? That seems like a shockingly small number.
Edit: I don’t understand the downvotes. My question on understanding behavior is perfectly reasonable. I don’t understand how this new technology works. What is it doing under the hood?
That's not what's happening in the demo. It's effectively updating 2000 virtual DOM nodes at 60 frames a second by scheduling updates so as many things are updated in each 16ms frame as possible. It'll scale with the device. If you have a beast of a computer it might update all 2000 every frame. If you're on a $100 smart phone it'll schedule the updates across several frames.
In the demo each node is a three.js box geometry - react-three-fiber uses react's virtual DOM reconciler to update state on three.js things. That doesn't have to be the case though. The nodes could be HTML elements or SVG things or any other browser renderable item. React doesn't care.
What the demo really shows is that concurrent mode React moves the bottleneck out of the framework and back to the browser - how fast the UI can be updated will be down to the browser instead of what the JS framework can do. That's a really big deal. It'll make writing performant UIs a lot easier which is good for everyone.
at second 1 I have 2000 updates remaining(+2000 new)
At second 2 I have 3500 updates remaining (+2000 new, -500 processed)
At second 3 I have 5000 updates remaining (+2000 new, -500 processed)
That is, my backlog will indefinitely grow larger, unless it's dropping updates.
Javascript runs a "main loop" that just executes "tasks". Tasks are chunks of synchronous code, and are not preemtible.
Let's say you have a "generator" task that generates 2_000 "update" tasks. The way this works is:
1. "generator" schedules 2_000 "update" tasks to run as soon as possible, and schedules another "generator" task for 1s from now.
2. The browser starts running "update" tasks one after the other as fast as it can.
3a. If the "update" tasks are all done 1s after the previous "generator" task, then the browser will run "generator" again.
3b. If they are not done, the browser will continue running "update" tasks and only invoke "generator" again _after_ it has finished with all the "update" tasks, be it 1 or 100 seconds after the previous "generator" task.
All members of the staff -- environment artists, character artists, level designers, animators, FX artists -- are all very technical and have the power and wisdom to use the framerate wisely.
I'm not even sure why you're bringing up frustum culling -- you're suggesting that we have too many objects to run the cull math on so we schedule across frames? But we don't; that results in visual popping. If culling is a bottleneck, we usually solve by broad-phase data structures like octrees, or ask the artists to condense multiple separate models into one so we have less objects to manage (another big cheat, artist labor).
Games are not using complex scheduling and multithreading to achieve acceptable performance updating 2000 entities. You can achieve that easily on a single thread by laying your data out to take advantage of locality of reference and the CPU cache.
What games do is fundamentally different than what react is doing here. In games there are many places where you can trade off quality for speed (for instance frustrum culling). You can decide to compute less to fit within your computation time slice. Likewise, physics can take into account the update rate to adjust to the CPU/GPU's processing capability (i.e: you can use a delta_t in your computations to adjust for frame differences).
In the website there are some such opportunities (you could do it with animations for instance), but you cannot do it in general because most apps logic would not tolerate it (yeah sorry I dropped your request callback bad luck). For this reason I would be very surprised if React went that route.
And frustum culling has nothing to do with this nor it reduces any amount of updates.
The point is that you shouldn't need a beast of a computer to update 2,000 boxes. That's way outside of the performance profile I expect from any recent CPU.
There are many reasons you don't write a shopping cart in Unity, but now you can get a taste of performance nevertheless.
Props to the React team. (Yes that is a joke; I am passing props to the React team ;D)
Paul’s work here (react three fiber) is actually a very thin layer on top of react. Which is impressive in itself. In 3 files he was able to bond all of react to all of three JS. Which says a lot on react and on Paul’s work.
But the point is: it’s react general ideas that are at play here
edit: and the real innovation here appears to be writing a multi-threaded scheduler. You should not need to spin up multiple threads to handle 2,000 objects. Something is going seriously wrong perf-wise here.
Beyond work-arounds like pagination/hiding data, I've found the best solution is being careful with what data is marked as Observable/reactive and tracked by Vue for changes...which in Vue 2 is the automatic default behaviour where everything put it into data()/computed/Vuex or elsewhere is monitored. Then on top of that functionality which updates tons of rows at once (for example drag and dropping a row which then updates { order: n } for a hundred objects) then resulted in mini-freezes/slow downs.
Crawling through dev tools memory captures never helped much because Vue is already extremely async/concurrent, so there was never one big slow thing to refactor - the problem was many thousands of small things that turned into one big slow thing.
Vue 3 (surprisingly) helped me solve this problem, not by adding some extra concurrency to the view layer, but by making all the data default to a simple JS module, instead of a big options interface in Vue 2 where everything in Observable by default, and instead you explicitly mark which stuff should be reactive and watched. I managed to reduce the number of observed objects by 50-75% in some cases which got rid of any slow-downs.
This of course was a rare use-case and not a knock on Vue, most people aren't managing hundreds of objects in a single HTML UI/form but it also wasn't very extreme either - just a modern web-based data-heavy B2B software requirement.
Not all of it is do to the inherent framework design either but about how you use it + having good best-practices and defaults.
I'm sure there was ways to do the same in Vue 2 but being the default approach significantly helps organize the code in a performant way. Only the data which needs to be reactive will be reactive and everything else is simple fast JS objects.
I'd much rather the default be non-observed and the reactive data is explicitly marked, instead of the opposite. Which is a surprisingly under-reported evolution in Vue 3.
What algorithm? If that's about diff/reconciliation of DOM/VDOM then it is worse than that - O(n^3).
I can see it being a bit more complex if you need to track how nodes move across different parents. But it seems like react doesn't handle this: https://github.com/facebook/react/issues/3965#
In a broader context those metrics are pathetic. Your OS for example has UI performance light years faster.
state-connected: components with changeable internal state (as distinct from stateless components that always render the same way based on their inputs)
Yes this is a sour comment, made by an elitist that can no longer muster the energy to be compassionate and walk towards the light people that seem to have taken a vow not to experiment, evaluate, judge and, most importantly, use their brain and previous experience.
All the offensive words in this post have been chosen with care for their dictionary definition. Now go ahead, complain and downvote.
React would like to pause rendering to react to new events as they come in. However, there is no way for React to be told when there is a new event, so it has to poll for it. Unfortunately there's no way to directly poll for a new event, so it has to fake an event poll via inversion of control built on top of requestAnimationFrame.
useFrame binds a single component to the render-loop. But in a managed way. Once the component unmount, it's getting taken out. You can also stack calls like that with something like a z-index, which is awesome for effects.
This game uses some of it: https://codesandbox.io/embed/react-three-fiber-untitled-game...
I haven't looked at the details for web, but I think it does webasm and canvas, etc. and with the architecture they have it could potentially be a lot faster than traditional JS frameworks https://flutter.dev/web
It is such a bad fit for games and it is very disrespectful of the environment and your users' battery.
React is just a procedural method of UI rendering. That's what pretty much all games do already. So is the question.
Or some of the other demos here: https://github.com/react-spring/react-three-fiber
React lends extremely well to games, especially with hooks. Now you have reactive components, but also algebraic effects.