6 ms·
The creator of React-Three-Fiber has come out with some extremely bizarre tweets, like claiming updating 2,000 cubes at 60fps is an "impossible amount of load"
by Jasper_ 5y ago
The creator of React-Three-Fiber has come out with some extremely bizarre tweets, like claiming updating 2,000 cubes at 60fps is an "impossible amount of load" [0].
The solution it uses for performance is... punting your calculations to the next frame if you didn't make it this frame. They call this "the scheduler". Turns out this makes no sense in any serious context if you want your 3D frame to be coherent.
I'm sure this is helpful for some people, but I can't rely on it when making serious applications. There's lots of things you can punt to the next frame, but ideally that should be decided per system, and not a global framework.
Keep in mind that rendering here is still happening every frame, so the only thing that's happening is 2,000 transform updates for cubes. Inexplicably, without the scheduler that punts updates to the next frame, it takes 700ms to do this [1]. For 2,000 matrix muls. That's 3ms per cube. What on earth is this doing?
[0] https://twitter.com/0xca0a/status/1199997552466288641 https://twitter.com/0xca0a/status/1199997552466288641
[1] https://twitter.com/0xca0a/status/1199997561358213120 https://twitter.com/0xca0a/status/1199997561358213120
- vvanders 5y agoThe "No. There is no additional overhead. Components participate in a unified renderloop outside of React. It outperforms Threejs in scale due to Reacts scheduling abilities." made me do a serious double-take. If you want to go fast in this space then you need to care about data layout and how the system is structured end-to-end. Calling a function per object is going to hit a wall regardless of how you schedule. Hundreds/Thousands of updates is not small but it's also not massively impressive either. I've done ~2,800 node scene graphs on underpowered ARM chips back in '09 at 60FPS including rendering. You have to use NEON, and be aware of your caches. No scheduling magic is going to change that unless you're just deferring work which sounds like what may be happening there. FWIW I've also done this in Java via FlatBuffers(which uses ByteBuffer internally) to keep data coherency when driving animations frames so it doesn't require dropping down to C/C++/Rust(although C#'s value types do make it easier).
- brundolf 5y agoIt's interesting because in normal React usage, the overhead of generating and throwing away a bunch of objects on each update is dwarfed by the cost of updates to the DOM But in Three.JS the actual updates to the tree should be cheap, right? There's no reflow, you're just setting values in memory to be used by the next render frame. If so, that changes the calculus. There's also the fact that in an app, it's rare for actual state updates (and therefore React renders) to happen on every frame; usually it's only on interactions. Maybe the occasional animation (if it can't be handled by native CSS animations). Whereas in graphical contexts like this, it's much more likely you'll have lots of objects in continuous motion (and therefore continuous re-renders). I can see the productivity gains being worth it for a lot of simpler use-cases, but I'm skeptical about the performance claims when you start to get into complex scenes with lots of entities.
- vvanders 5y agoYeah, I can totally see the argument that it's a programming model that's well understood and you can get a productivity boost from that. However I don't think you can say that doesn't come with a cost, otherwise it would have been pretty widely adopted across the industry.
- brundolf 5y ago> otherwise it would have been pretty widely adopted across the industry I'm not sure that's a fair explanation for why. It's totally possible to come up with new paradigms that are useful even though nobody's thought of them before. I would think the main issue will be around JavaScript's tendency (cultural, syntactic, etc) to casually create and release objects all over the place, constantly. There's nothing intrinsically wrong with this, but it seems problematic for this use-case. Example: JavaScript doesn't have named function parameters, because instead you just create and destructure an object: function foo({ param1, param2, param3 }) { } foo({ param1: 'a', param2: 'b', param3: 'c' }) The syntax encourages this, the React docs encourage this. JSX itself does this for every element you render. And for normal JavaScript usecases it works just fine. But when you're running this logic every frame, I would guess it will limit you at a certain point. Despite that, I think people are onto something with the broader idea of coding a 3D scene declaratively. I'm just skeptical that React or its norms are the right path to doing it at scale.
- short_sells_poo 5y agoI can't take a 3d rendering framework seriously if it gets bogged down at updating 2000 untextured and non-interacting cubes @ 60 FPS. What on earth is all that time spent on? Doing ~2000 3x3 matrix ops should take a modern processor a few hundred usec surely? To put the silliness of the 2000 number in context, look here at a showcase of the Unity ECS system from years (!) ago: https://software.intel.com/content/www/us/en/develop/articles/get-started-with-the-unity-entity-component-system-ecs-c-sharp-job-system-and-burst-compiler.html https://software.intel.com/content/www/us/en/develop/article... Their starting setup achieves 16000 textured and complex (relatively) models @ 30fps. Which is already much more than the "optimized" thing is achieving here. And it doesn't "cheat" by pretending to be fast via simply not doing to updates that are expected. And once they apply the various optimizations with memory layout etc... they get to 150000 (!) textured models moving about on screen @ 30 fps. So let's say 75000 @ 60fps, which is more than 35x as many objects, and the objects are much more complex. Am I missing something? Why is "2000 cubes @ 60fps" extraordinary?
- Jasper_ 5y agoYou have to remember that this 700ms overhead is not the renderer. It's just setting fields on the underlying Three.JS Cube objects. The Three.JS renderer runs fine, and I don't even consider Three.JS to be a fast renderer. So yes, the overhead of React-Three-Fiber's tree reconciliation is seemingly massive. No, I do not have any answers for what it is doing. Nor do I have any guesses.
- short_sells_poo 5y agoThanks for clarifying. 700ms overhead is massive. It sounds like the approach is just deeply suboptimal for any sort of high performance and complicated 3d scenes. Perhaps that was not their objective...
- mlsarecmg 5y agothere were multiple versions of that test, the fist had nothing to do with the subject matter, the ones that people refer to (spinning cubes) had an artificial delay that was added to simulate cpu stress.
- hitekker 5y agoThe claim that a general purpose web frontend framework adds no overhead to GPU intensive animation is indeed bizarre. The author seems to believe his own marketing that his framework is the end-all-be-all.
- felipellrocha 5y agoThe “no additional overhead” struck me as a really high claim, and this confirms it. 2000 cubes at 60fps is really nothing if you know what you’re doing.
- mlsarecmg 5y agor3f does not introduce overhead. the readme text explains it all https://docs.pmnd.rs/react-three-fiber/advanced/scaling-performance#enable-concurrency https://docs.pmnd.rs/react-three-fiber/advanced/scaling-perf...
- felipellrocha 5y agoI’ve drawn 100s of 1000s of objects per frame in the past. 2000 is nothing.
- mlsarecmg 5y agoas absurd as it may seem to you, this is a react feature. the upcoming react 18 release is pretty much based on concurrency. this is the test you are referring to: https://docs.pmnd.rs/react-three-fiber/advanced/scaling-performance#enable-concurrency https://docs.pmnd.rs/react-three-fiber/advanced/scaling-perf... the github repo now contains a vanilla test that you can run
- Jasper_ 5y agoAs I said, "schedule" just means "defer work to future frames". That's a valid strategy if you have severe load, but it's not globally applicable: one might imagine that two components need to be updated together (e.g. updating the arms and body of a character should not be split up between two frames, or else the user will see split bodies for in-between frames). At the very best, this sort of scheduling should be informed by gameplay systems. It can "effortlessly outperform" only if we're talking about throughput, not latency here -- the scheduler ensures that less is done each frame so we can meet 60fps each frame, at the expense of having things done frames later than when they probably should have been. I'm aware this is all a React feature. I disagree with the React team that "concurrency" is a usable solution to performance in all cases. But I can respectfully disagree with them about that. I can understand why a scheduler helps improve user-perceived performance. I'm happy to talk about what I believe are the tradeoffs. Regardless of all of that, updating 2,000 cubes should never cause 700ms of load to begin with. The test you linked me to is not the same test. The test in the tweet is seen here [0], and has no artificial runtime delay as far as I can tell. For extra irony, note that they already have to bypass React (which is described as ItemSlow), in favor of the "Zustand approach", aka modifying things imperatively. Remember, Three.JS is already running every frame, and rendering every frame, with or without a scheduler. That means that the 700ms overhead has to be coming from the React / R3F part of the demo. [0] https://github.com/pmndrs/react-three-fiber/blob/e3a71baad421a8b431866483d7d519e90c604206/examples/demos/dev/Benchmark.js https://github.com/pmndrs/react-three-fiber/blob/e3a71baad42...
- mlsarecmg 5y agowhat you link there has more to do with react vs zustand. if it interests you, read up on react 18 concurrency, this is the bit you are missing in this discussion.
- dheera 5y ago> updating 2,000 cubes at 60fps Have we gotten so bloated with framework on top of framework that even this is deemed an impossible load? If you write straight-up WebGL code 2000 cubes at 60fps should be a walk in the park for any modern PC.
- mlsarecmg 5y agothreejs has no problem rendering hundreds of thousands of objects, so react doesn't have a problem either because its baseline is threejs, it does not introduce overhead or a performance penalty. These components render in a regular RAF loop, react is not involved at all unless you are mounting/unmounting objects, and this is where it can actually be faster.
- nbardy 5y agoYea. This could be nice for small demos and toy apps. But if you want to drive a canvas based render context. You're going to want to control the render loop for your own application. Performance there is domain specific.