logo

drewdevault.com

[mirror] blog and personal website of Drew DeVault git clone https://hacktivis.me/git/mirror/drewdevault.com.git

Thoughts-on-performance.md (5395B)


  1. ---
  2. date: 2020-02-21
  3. title: Thoughts on performance & optimization
  4. layout: post
  5. tags: [performance]
  6. ---
  7. The idea that programmers ought to or ought not to be called "software
  8. engineers" is a contentious one. How you approach optimization and performance
  9. is one metric which can definitely push my evaluation of a developer towards the
  10. engineering side. Unfortunately, I think that a huge number of software
  11. developers today, even senior ones, are approaching this problem poorly.
  12. Centrally, I believe that you cannot effectively optimize a system which you do
  13. not understand. Say, for example, that you're searching for a member of a
  14. linked list, which is an O(n) operation. You know this can be improved by
  15. switching from a linked list to a sorted array and using a binary search. So,
  16. you spend a few hours refactoring, commit the implementation, and... the
  17. application is no faster. What you failed to consider is that the lists are
  18. populated from data received over the network, whose latency and bandwidth
  19. constraints make the process much slower than any difference made by the kind of
  20. list you're using. If you're not optimizing your bottleneck, then you're
  21. wasting your time.
  22. This example seems fairly obvious, and I'm sure you, our esteemed reader, would
  23. not have made this mistake. In practice, however, the situation is usually more
  24. subtle. Thinking about your code really hard, making assumptions, and then
  25. acting on them is not the most effective way to make performance improvements.
  26. Instead, we apply the scientific method: we think really hard, *form a
  27. hypothesis*, make predictions, test them, and then apply our conclusions.
  28. To implement this process, we need to describe our performance in factual terms.
  29. All software requires a certain amount of resources — CPU time, RAM, disk
  30. space, network utilization, and so on. These can also be described over time,
  31. and evolve as the program takes on different kinds of work. For example, we
  32. could model our program's memory use as bytes allocated over time, and perhaps
  33. we can correlate this with different stages of work — "when the program
  34. starts task C, the rate of memory allocation increases by 5MiB per second". We
  35. identify bottlenecks — "this program's primary bottleneck is disk I/O".
  36. When we hit performance problems, then we know that we need to upgrade to SSDs,
  37. or predict what reads will be needed later and prep them in advance, cache data
  38. in RAM, etc.
  39. Good optimizations are based on factual evidence that the program is not
  40. operating within its constraints in certain respects, then improving on those
  41. particular problems. You should always conduct this analysis before trying to
  42. solve your problems. I generally recommend conducting this analysis in advance,
  43. so that you can predict performance issues before they occur, and plan for them
  44. accordingly. For example, if you know that your disk utilization grows by 2 GiB
  45. per day, and you're on a 500 GiB hard drive, you've got about 8 months to plan
  46. your next upgrade, and you shouldn't be surprised by an ENOSPC when the time
  47. comes.
  48. For CPU bound tasks, this is also where a general understanding of the
  49. performance characteristics of various data structures and algorithms is useful.
  50. When you know you're working on something which *will become* the application's
  51. bottleneck, you would be wise to consider algorithms which can be implemented
  52. efficiently. However, it's equally important to re-prioritize performance when
  53. you're not working on your bottlenecks, and instead consider factors like
  54. simplicity and conciseness more seriously.
  55. Much of this will probably seem obvious to many readers. Even so, I think the
  56. general wisdom described here is institutional, so it's worth writing down. I
  57. also want to call out some specific behaviors that I see in software today which
  58. I think don't take this well enough into account.
  59. I opened by stating that I believe that you cannot effectively optimize a system
  60. which you do not understand. There are two groups of people I want to speak to
  61. with this in mind: library authors (especially the standard library), and
  62. application programmers. There are some feelings among library authors that
  63. libraries should be fairly opaque, and present high-level abstractions over
  64. their particular choices of algorithms, data structures, and so on. I think this
  65. represents a fundamental lack of trust with the programmer downstream. Rather
  66. than write idiot-proof abstractions, I think it's better to trust the downstream
  67. programmer, explain to them how your system works, and equip them with the tools
  68. to audit their own applications. After all: your library is only a small
  69. component of *their* system, not yours — and you cannot optimize a system
  70. you don't understand.
  71. And to the application programmer, I urge you to meet your dependencies in the
  72. middle. Your entire system is your responsibility, including your dependencies.
  73. When the bottleneck lies in someone else's code, you should be prepared to dig
  74. into their code, patch it, and send a fix upstream, or to restructure your code
  75. to route the bottleneck out. Strive to understand how your dependencies, up to
  76. and including the stdlib, compiler, runtime, kernel, and so on, will perform in
  77. your scenario. And again to the standard library programmer: help them out by
  78. making your abstractions thin, and your implementations simple and debuggable.