More recent commentators have noted a corollary - for software projects with a long lifetime of code reuse, such as Microsoft Windows, the structure of the code mirrors not only the communication structure of the organization which created the most recent release, but also the communication structures of every previous team which worked on that code.
I don’t think the percentage of time waiting for input has anything to do with this. Outside of video games, the way most people will see performance problems is in the latency of their UI interactions. You press a button and want to see the result as fast as possible.
In other words, the user’s entire perception of your program’s performance falls into that 0.1%.
Performance is not an absolute. At the end of the day it is about user experience. From a computer science point of view, we can measure memory and CPU usage, but if the users haven't been complaining then what problems are you actually solving (at least from a product POV)?
Performance for performance sake is an interesting and appealing challenge to us engineers. I was writing C code in the 90s and I miss being that close to the hardware, trying to spare every clock cycle that I could while working with machines that had sparse resources.
But today I'm building SaaS products for millions of simultaneous active users. When customers complain about performance it is often not what us engineers think of as "performance." They're NOT saying things like "Your app is eating all memory on my phone" or "the rendering of this table looks choppy." It's usually issues related to server-side replication lag causing data inconsistencies or in some cases network timeouts due to slow responding services.
The point is the age old advice that we were giving aspiring game programmers back in the 90s:
Figure out and understand your priorities.
The famous inverse square root function in the Quake III Arena source code is a great example. If memory serves me, they needed this calculation as part of their particle physics engine. The problem is that calculating inverse square roots precisely is very expensive, especially at the scale they were required to. So they exploited how 32-bit floating point numbers are represented in binary in order to do a fast, good enough approximation. This is a good example of a targeted, purposeful optimization.
Back in the 90s we were obsessed over getting the most out of our hardware, especially when coding games. So we picked up all sorts of performance hacks and optimizations and learned how to code in assembly so that we could get even closer to the bare metal if we needed to. The result was impossible to understand and maintain code and so experienced engineers taught us young'uns:
Write clean code first, then profile to understand what your bottlenecks are, then to make TARGETED optimizations aimed at solving performance issues in order of priority.
That priority always being driven by user experience and/or scalability requirements.
Anything else is premature optimization. You're speculating about where your performance bottlenecks are, and you're throwing out maintainable code for speculative reasons; not actually knowing how much of an impact your optimizations are going to have on user experience.
I agree with almost every one of your points. I think I oversimplified my original point for the sake of clarity.
If you are throwing out maintainable code for the sake of performance, it had better be because you know that it's your bottleneck, and that the performance increase is worthwhile in the first place. "Performance for performance sake" shouldn't exist anywhere outside of hobby projects.
I would argue that responsive user interfaces are really important to user experience. Not many people are complaining because everyone is used to unresponsive apps, but that doesn't mean users wouldn't appreciate a more responsive app.
I would also add that there isn't always a tradeoff between performance and maintainability. If you can adopt some performant coding patterns that don't sacrifice maintainability, then absolutely do that. I think Casey's example of "switch-based polymorphism" is one such pattern(and I think the fact that Rust took a similar route to polymorphism is a vote in favour of this pattern).
Software engineering is about solving real problems. The only time you need to write malleable software is when the problem needs a malleable solution.
Pushing for everyone to write malleable software sounds like telling people to forget about the particular problems that they are solving, and telling them to solve a general problem instead. Now you’re writing a complicated system for general use, rather than a simple solution for a well-defined problem. Your solution will now take longer to develop, be harder to understand and MUCH harder to test properly (way more use cases).
Obviously some problems need general solutions. But if every problem needed a general solution nobody would get anything done.
> The only time you need to write malleable software is when the problem needs a malleable solution.
The point is that it’s very difficult to account for this beforehand, especially as the author of the software. (“What do you mean we should make this an option? 5 works for me, and I’ll change it in the code if it doesn’t!”) When designing software, it’s always nice to give a thought to “if I were using this and I wanted to change it, how would I do so?” and seeing if there’s anything you can do to improve that.
> These days, I think most users will lose more time and be more frustrated by poor UI design, accidental inputs, etc. than any performance characteristics of the software they use.
I’m willing to bet that a significant percentage of my accidental inputs are due to UI latency.
Virtually all of my accidental inputs are caused by application slowness or repaints that occur several hundred milliseconds after they should have.
I want all interactions with all of my computing devices to occur in as close to 0ms as possible. 0ms is great; 20ms is good; 200ms is bad; 500ms is absolutely inexcusable unless you're doing significant computation. I find it astonishing how many things will run in the 200-500ms range for utterly trivial operations such as just navigating between UI elements. And no, animation is not an acceptable illusion to hide slowness.
I am with the OP. "Good enough" is a bane on our discipline.
How about the i-am-about-to-press-this-button-but-wait-we-need-to-rerender-the-whole-page. At which point you misclick or not at all. Especially some recent shops and ad heavy pages use this great functionality ;)
The rule for games is that you have 16ms (for a 60Hz monitor) to process all input and draw the next frame. That's a decent rule for everything related to user input. And since there are high refresh-rate monitors, and it's a web app and not a game using 100% CPU & GPU, just assume 4-5ms for a nicer number. If you take longer than that to respond to user input on your lowest-capability supported configuration, you've got a bug.
0ms is great, 4ms is very good, 16ms is minimally acceptable, 20ms needs improvement (you're skipping frames), 200ms is bad (it's visible!), 500ms is ridiculous and should have been showing a progress bar or something.
Responding to input doesn't necessarily mean being done with processing, it just means showing a response.
Don’t get me started with all the impressive rotating zooming in Google Maps every time you accidentally brush the screen.
The usage story requires you to switch to turn-by-turn, and there’s no way to have bird eye map following your location along route (unless you just choose some zoom level and manually recenter every so often.)
It’s awful, distracting and frankly a waste of time... just to show a bit of animation every time I accidentally fail to register a drag...
Well, Google Maps is its own story - it's like the app is being actively designed to be as useless as possible as a map - a means to navigate. The only supported workflow is search + turn-by-turn navigation, and everything else seems to be disincentivized on purpose.
I actually think it's actively harmful to hide problems that can be otherwise fixed. If the CPU is too busy to keep filling the audio buffer, the solution is to increase the buffer size to put less stress on the scheduler. I recently reduced my buffer size in Ableton Live, but I knew I had to increase it because I could hear pops. If these pops were being covered up, I wouldn't have realized my buffer size was too small and I'd be unknowingly introducing subtle artifacts into every recording.
"Undefined behavior" is a broad area which covers everything from defects in a program that make it crash, to documented language and library extensions (which real-world programs can hardly avoid using).
The problem is that, just because it's not causing problems now, doesn't mean it can suddenly start causing incredibly hard to track down problems years later after an innocuous compiler upgrade...
More recent commentators have noted a corollary - for software projects with a long lifetime of code reuse, such as Microsoft Windows, the structure of the code mirrors not only the communication structure of the organization which created the most recent release, but also the communication structures of every previous team which worked on that code.