6 ms·
React, Inline Functions, and Performance
- kitten_mittens_ 9y agoOverall, I think the post is a little too dismissive about the performance implications of bind, and says a new function trigger rerenders. In IE 11 and Safari for example with Babel transpilation, arrow functions are going to eat your frame rate alive, especially if you have a text input handler with some sort of validation around it. Even in latest Chrome and Firefox, you end up with inputs that feel slow. I agree PureComponent and object equality cancel the benefits of each other out a good deal of the time. The pure without handelers approach has the notable exception of a sortable table with inline editing, or even checkboxes, where row index of the handler matters. Ultimately, it is better to make new devs to react aware of avoiding bind in render, than it is to retroactively add constructor placed binds in the app when frame rates are below 25-30 by default. Something I’ve seen quite a bit in redux apps.
- JasonSage 9y ago> In IE 11 and Safari for example with Babel transpilation, arrow functions are going to eat your frame rate alive, especially if you have a text input handler with some sort of validation around it. Even in latest Chrome and Firefox, you end up with inputs that feel slow. Except, the author writes that there isn't quantitative data to back up this kind of claim. It's exactly the kind of armchair performance speculation that they're trying to refute! Can you cite a benchmark or real-world app which exhibits this kind behavior? I'm not saying you're wrong, but the author deals with tons of codebases and tons of developers and says that this comes up a lot but there's never any data to back it up.
- Silhouette 9y agoThe author dismisses a lot of things as speculation that several years of hard data on my own projects say can be real. It doesn't take much in the way of large tables or lists or SVGs to be in a situation where your costs for rendering and/or reconciliation with the real DOM are significant. The bottleneck typically isn't the time to build the new function every time you render the React component. The problem is that doing so can remove all of the benefit you would otherwise get from using a pure component (or otherwise implementing shouldComponentUpdate), because each function you pass in as a prop will have a new identity and therefore will not compare equal to the previous one, even if in reality it is calling the same underlying function with the same parameters so nothing material has changed.
- acemarke 9y agoLegitimate request: can you provide some actual benchmarks or examples that demonstrate these perf issues? Part of the reason why Ryan, Michael, and others like myself are pushing back on this is that "creating functions kills perf!" has become a kind of "received wisdom" that people are blindly and dogmatically passing around, yet there don't seem to be hard numbers to actually back up what everyone "knows" to be an issue. Ryan did also address the `shouldComponentUpdate / PureComponent` aspect in the post already.
- Silhouette 9y agoLegitimate request: can you provide some actual benchmarks or examples that demonstrate these perf issues? Not straightforwardly (due to pseudonymity and in any case client confidentiality) but I can give you a typical example. I've worked on quite a few web apps in recent years that have used SVGs to draw somewhat complicated and interactive visualisations. That needs a level of efficiency well beyond your basic "Here's a control, here's the underlying data in the model, there's roughly a 1:1 correspondence between them" web app UI. For example, some of the layout algorithms could take a significant fraction of a second just to run once, so typically you get a whole extra level of cache sitting between your underlying data and your React-based rendering functionality. Even then, a rerender of the whole visualisation can easily take a significant fraction of a second, just due to the number of elements involved and the relatively poor performance of browsers at handling large inline SVGs in various situations. So, you immediately need to start isolating which parts of the SVG actually need updating in response to user events, including relatively frequent ones like mouse moves or repeated keypresses. But if many of those child components are also going to be interactive, there are potentially hundreds or thousands of event handlers flying around. If each type of interaction needs a handler that has some common information, like say where your underlying controller logic lives, and also something specific to each element, like say the ID(s) of some underlying item(s) in your data model that are related to that part of the visualisation, then either you get a lot of binding going on or you have to pass in a common function and manually call it with the extra parameters from the event handler functions for your SVG elements. One thing you really don't want to do in this situation is have function involved that is (a) recreated on each render of (b) a high-level component with a lot of children that will ultimately be calling that function. Doing so will inevitably mean that you need to rerender all such children in your viz instead of just a very small number that might be relevant to say a particular mouse move or keyboard event. The thing that gets me is that in this sort of environment, whether it's an SVG or a table or a list or any other part of the DOM that will contain a lot of content and probably be based on a lot of different underlying data points in your model, it's entirely predictable that you will have to focus on only narrow parts of your rendered content in response to frequent user-triggered events. So you know right from the start that you can't just bind your event handling functions at the top level and pass them down through props and add more specific parameters as you pass them into more specific child components. That entire strategy is doomed before your write a line of code (though unfortunately it will probably work just fine if you're developing with a very small set of data and a very simple resulting visualisation, which can create a false sense of confidence if you're not careful). So you might as well plan the design for your event handling and callbacks around what you're inevitably going to need from the start. I can't actually give you hard data on the exact FPS we saw the first time we tried the bad way of doing this, but I can tell you that it was more like SPF than FPS. Ryan did also address the `shouldComponentUpdate / PureComponent` aspect in the post already. Honestly, the last parts of the article about ignoring handlers didn't make much sense to me anyway. I can't see why you'd ever want to pass props into a React component that weren't ultimately going to influence rendering one way or another, and therefore why you'd ever be in a position where you could safely ignore props for shouldComponentUpdate purposes yet wouldn't just remove them altogether. Certainly any assumptions about event-handling on<X> not needing to be diffed would be totally wrong in the context of a table/list/SVG/etc. where each child element is tied to some specific underlying data point(s) and has event handler functions supplied via props accordingly. That looked like another of those cases where the author is generalising their own limited experience and failing to realise that for plenty of other situations the same assumptions won't apply.
- davedx 9y agoI find this claim incredible. 25-30 fps below default? I've used React on set top boxes with performance characteristics of old Android phones, and even then, our perf bottlenecks were not caused by function binds (it was usually CSS reflows/paints). How many inputs are you rendering with React to see this kind of slowdown?
- al2o3cr 9y agoIf the UI is running so much JS that it needs a microoptimization to run smoothly, the problem isn't that people don't remember to do the microoptimization.
- Bahamut 9y agoI have not had this problem with Safari (at least the latest) - the app I work on is pretty snappy, minus api requests taking some time.
- arstin 9y agoJust to add my own "limited experience" in support of this article, I've ran into the slow text input problem many times, but never once solved it by getting rid of arrow functions in render. It just doesn't seem to matter that much (not saying a dedicated text input component shouldn't try to get all the details right...whatever they might be). In my experience a choppy text input has actually been a problem with the redux architecture, or layout, or some wild child components, and so on.
- hliyan 9y agoI'm irked by the frequent use of "premature optimization is the root of all evil" without actually comprehending what Donald Knuth meant to say: "We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%". This quote is from an era where hardware resources were at such a premium that developers obsessed excessively over the performance impact of every line of code.
- joshuata 9y agoAlso, you have to design for performance. There are plenty of ways to architect code that lock you out from future optimizations. I agree that small optimizations such as different referencing techniques should be measured, but you should be able try both out, and "premature optimization is evil" often keeps you from optimizing ever.
- guscost 9y agoThe way I've used (and would recommend using) this idea is to spend some time deciding whether any big optimization is premature before implementing. By definition, premature optimizations cause more problems than they're worth, but appropriate optimizations pay dividends. You're absolutely right that the 97% number comes from a different time, but the central phrase is approximately as true as it ever was. The trick is that it can be really hard to determine whether a given optimization is premature. Like "one of the hardest things a working programmer will ever attempt" hard. One more thing: We optimize for factors other than CPU time, and all of the above still applies.
- marcosdumay 9y agoI do agree overall, but this fits the premature optimization case very well. Inlining/declaring functions is a micro-optimization that has no impact on the overall architecture, can be done/undone later without any problems, is a great target for optimizing in another level (interpreter, compactor, web-server), and will certainly not lead to any non-linear gain or loss. That quote was warning exactly about this kind of optimization, and it's foolish to trade anything (development time, maintainability, whatever) for that optimization without a business case and measurements.
- n0us 9y agoAlmost all of our functions are inlined and furthermore we use the () => syntax on class methods instead of manually binding them in the constructor. I've only run into performance issues because of this once and it was fixed by using componentShouldUpdate. This whole debate seems like one of those memes that hits a nerve one way or the other with a lot of people but has little bearing on real life.
- always_good 9y agoWe have performance memes because profiling is hard and we're often unable to get a hint beyond our intuition. People like to act like you just open a profiler and see actionable data every time, but that's under ideal circumstances. In a busy application, you're more likely getting nickel and dimed by more subtle things. And your flame graphs stacked with React/library internals that don't point to a single problem in your code. And you have to have the skills to recognize and verify small performance gains when you do have them. If that stuff doesn't line up, then you're stuck with little more than superstition and hoping other people's solutions generalize over your problems.
- Ciantic 9y agoThe poster argues in a source code comment that `handleStuff = () => {}` is risky: "this is nice, but it isn't JavaScript, not yet anyway, so now we need to talk about how TC39 works and evaluate our draft stage risk tolerance" I don't see that particular feature a risky to use, it's so wide spread in Babel / TypeScript that if it were ever overturned there could be a automatic transpiling solution to fix all that.
- arenaninja 9y agoWe're still working on our own first truly large scale React application, and while we haven't seen significant memory pressure from inline functions, I still think it makes it harder to debug I was trying to fix a focus issue on a component so I hooked in to componentWillUpdate to see what was happening, and it was ridiculously noisy. I added a function to diff the state and props objects and most of the time the only changes were the inlined functions. I removed the inlining and I could debug again as only 4 diffs occurred and they were actual props/state changes, hooray! It may be that there's zero performance gain from avoiding them, but when you work with a large complex application any time you can save debugging is a blessing. I may not avoid inlined functions wholesale now, but I will definitely do so for complex components where they will need to be debugged
- marcosdumay 9y agoThat is a problem of emulating functional behavior in a language that is not functional at its core. In functional languages you mostly don't debug, you reconstruct your functions in a controlled environment while you look at the intermediate results. That works everywhere except on a tiny effectfull portion of your code, where you have to actually debug like the imperative code that it is. In Javascript that effectfull portion is usually all the code, and even the places that could be reconstructed at will you will have to debug first to be sure they haven't side effects.
- arenaninja 9y agoFunny that you mention this, because part of the problem was that someone had made a fully functional wrapper around the component. Someone else made a second class wrapper around the functional one because the component has text so state is needed. I wrote out the functional wrapper entirely and kept maybe one of the functions in the class wrapper Now I think a great many things can be made functional in JavaScript, but purists ruin this for everyone else.
- marcosdumay 9y agoWe seem to be defining those words differently. From my position, it's impossible to create a pure wrapper for an interface widget.
- Silhouette 9y agoAsk any performance expert and they will tell you not to prematurely optimize your program. All of them. Yes, every single one of them. 100% of people with deep performance experience will tell you not to prematurely optimize your code. I have spent much of my career working on performance-sensitive and/or resource-constrained systems of one kind of another, and the above is simply not true. Performance is like security or reliability. Yes, sometimes you find local weak spots, and sometimes you can do something locally to improve them. Profilers are great and changes should be based on measurements of real performance; no argument from me there. But in general, these are global concerns. You can't always retrofit "acceptable performance" using a profiler and local changes down the line, any more than you can retrofit "robust security". If it matters for your application, it needs to be considered from day one, and your overall design at least needs to allow for doing the right things even if you choose to not then implement those things in full immediately. Needlessly busting your cache is a common source of performance problems on many modern platforms, and that is essentially what you're doing by passing inline functions as props when rendering React components whose render algorithm is expensive enough to use shouldComponentUpdate. Ironically, from the discussion later in the article, the author does seem to understand to some extent, so it's frustrating that they still lead with such a patronising introduction, and that they keep generalising their own experience when not everyone will be working on the kinds of projects where the same assumptions are valid. There are some useful ideas in this article, which I'm sure would be relevant to a lot of React-using developers, but they get a bit lost among the strange and/or dubious assumptions that underpin much of the author's arguments.
- baddox 9y agoDoesn’t “doing premature optimization” just mean “doing optimization before you ought to be,” and thus the phrase is a tautology if provided without any context or explanation? Obviously any useful advice isn’t just “don’t prematurely optimize,” but rather explains how to recognize premature optimization. I like the advice in this article to write things naturally until you have data that demonstrates that a refactor will improve performance. Obviously experienced engineers will make deliberate optimizations up front without measuring. A great example is making sure your ORM uses a join instead of making N+1 queries: http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations http://guides.rubyonrails.org/active_record_querying.html#ea.... Yes, a small app could probably get away with N+1 queries for a long time, but I wouldn’t consider writing this properly to be a premature optimization.