general_optimization.rst 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. .. _doc_general_optimization:
  2. General optimization tips
  3. =========================
  4. Introduction
  5. ------------
  6. In an ideal world, computers would run at infinite speed. The only limit to
  7. what we could achieve would be our imagination. However, in the real world, it's
  8. all too easy to produce software that will bring even the fastest computer to
  9. its knees.
  10. Thus, designing games and other software is a compromise between what we would
  11. like to be possible, and what we can realistically achieve while maintaining
  12. good performance.
  13. To achieve the best results, we have two approaches:
  14. - Work faster.
  15. - Work smarter.
  16. And preferably, we will use a blend of the two.
  17. Smoke and mirrors
  18. ~~~~~~~~~~~~~~~~~
  19. Part of working smarter is recognizing that, in games, we can often get the
  20. player to believe they're in a world that is far more complex, interactive, and
  21. graphically exciting than it really is. A good programmer is a magician, and
  22. should strive to learn the tricks of the trade while trying to invent new ones.
  23. The nature of slowness
  24. ~~~~~~~~~~~~~~~~~~~~~~
  25. To the outside observer, performance problems are often lumped together.
  26. But in reality, there are several different kinds of performance problems:
  27. - A slow process that occurs every frame, leading to a continuously low frame
  28. rate.
  29. - An intermittent process that causes "spikes" of slowness, leading to
  30. stalls.
  31. - A slow process that occurs outside of normal gameplay, for instance,
  32. when loading a level.
  33. Each of these are annoying to the user, but in different ways.
  34. Measuring performance
  35. ---------------------
  36. Probably the most important tool for optimization is the ability to measure
  37. performance - to identify where bottlenecks are, and to measure the success of
  38. our attempts to speed them up.
  39. There are several methods of measuring performance, including:
  40. - Putting a start/stop timer around code of interest.
  41. - Using the :ref:`Godot profiler <doc_the_profiler>`.
  42. - Using :ref:`external CPU profilers <doc_using_cpp_profilers>`.
  43. - Using external GPU profilers/debuggers such as
  44. `NVIDIA Nsight Graphics <https://developer.nvidia.com/nsight-graphics>`__,
  45. `Radeon GPU Profiler <https://gpuopen.com/rgp/>`__,
  46. `PIX <https://devblogs.microsoft.com/pix/download/>`__ (Direct3D 12 only),
  47. `Xcode <https://developer.apple.com/documentation/xcode/optimizing-gpu-performance>`__ (Metal only), or
  48. `Arm Performance Studio <https://developer.arm.com/Tools%20and%20Software/Arm%20Performance%20Studio>`__.
  49. - Checking the frame rate (with V-Sync disabled). Third-party utilities such as
  50. `RivaTuner Statistics Server <https://www.guru3d.com/files-details/rtss-rivatuner-statistics-server-download.html>`__ (Windows),
  51. `Special K <https://www.special-k.info/>`__ (Windows),
  52. or `MangoHud <https://github.com/flightlessmango/MangoHud>`__
  53. (Linux) can also be useful here.
  54. - Using an unofficial `debug menu add-on <https://github.com/godot-extended-libraries/godot-debug-menu>`__.
  55. Be very aware that the relative performance of different areas can vary on
  56. different hardware. It's often a good idea to measure timings on more than one
  57. device. This is especially the case if you're targeting mobile devices.
  58. Limitations
  59. ~~~~~~~~~~~
  60. CPU profilers are often the go-to method for measuring performance. However,
  61. they don't always tell the whole story.
  62. - Bottlenecks are often on the GPU, "as a result" of instructions given by the
  63. CPU.
  64. - Spikes can occur in the operating system processes (outside of Godot) "as a
  65. result" of instructions used in Godot (for example, dynamic memory allocation).
  66. - You may not always be able to profile specific devices like a mobile phone
  67. due to the initial setup required.
  68. - You may have to solve performance problems that occur on hardware you don't
  69. have access to.
  70. As a result of these limitations, you often need to use detective work to find
  71. out where bottlenecks are.
  72. Detective work
  73. --------------
  74. Detective work is a crucial skill for developers (both in terms of performance,
  75. and also in terms of bug fixing). This can include hypothesis testing, and
  76. binary search.
  77. Hypothesis testing
  78. ~~~~~~~~~~~~~~~~~~
  79. Say, for example, that you believe sprites are slowing down your game.
  80. You can test this hypothesis by:
  81. - Measuring the performance when you add more sprites, or take some away.
  82. This may lead to a further hypothesis: does the size of the sprite determine
  83. the performance drop?
  84. - You can test this by keeping everything the same, but changing the sprite
  85. size, and measuring performance.
  86. Binary search
  87. ~~~~~~~~~~~~~
  88. If you know that frames are taking much longer than they should, but you're
  89. not sure where the bottleneck lies. You could begin by commenting out
  90. approximately half the routines that occur on a normal frame. Has the
  91. performance improved more or less than expected?
  92. Once you know which of the two halves contains the bottleneck, you can
  93. repeat this process until you've pinned down the problematic area.
  94. Profilers
  95. ---------
  96. Profilers allow you to time your program while running it. Profilers then
  97. provide results telling you what percentage of time was spent in different
  98. functions and areas, and how often functions were called.
  99. This can be very useful both to identify bottlenecks and to measure the results
  100. of your improvements. Sometimes, attempts to improve performance can backfire
  101. and lead to slower performance.
  102. **Always use profiling and timing to guide your efforts.**
  103. For more info about using Godot's built-in profiler, see :ref:`doc_the_profiler`.
  104. Principles
  105. ----------
  106. `Donald Knuth <https://en.wikipedia.org/wiki/Donald_Knuth>`__ said:
  107. *Programmers waste enormous amounts of time thinking about, or worrying
  108. about, the speed of noncritical parts of their programs, and these attempts
  109. at efficiency actually have a strong negative impact when debugging and
  110. maintenance are considered. We should forget about small efficiencies, say
  111. about 97% of the time: premature optimization is the root of all evil. Yet
  112. we should not pass up our opportunities in that critical 3%.*
  113. The messages are very important:
  114. - Developer time is limited. Instead of blindly trying to speed up
  115. all aspects of a program, we should concentrate our efforts on the aspects
  116. that really matter.
  117. - Efforts at optimization often end up with code that is harder to read and
  118. debug than non-optimized code. It is in our interests to limit this to areas
  119. that will really benefit.
  120. Just because we *can* optimize a particular bit of code, it doesn't necessarily
  121. mean that we *should*. Knowing when and when not to optimize is a great skill to
  122. develop.
  123. One misleading aspect of the quote is that people tend to focus on the subquote
  124. *"premature optimization is the root of all evil"*. While *premature*
  125. optimization is (by definition) undesirable, performant software is the result
  126. of performant design.
  127. Performant design
  128. ~~~~~~~~~~~~~~~~~
  129. The danger with encouraging people to ignore optimization until necessary, is
  130. that it conveniently ignores that the most important time to consider
  131. performance is at the design stage, before a key has even hit a keyboard. If the
  132. design or algorithms of a program are inefficient, then no amount of polishing
  133. the details later will make it run fast. It may run *faster*, but it will never
  134. run as fast as a program designed for performance.
  135. This tends to be far more important in game or graphics programming than in
  136. general programming. A performant design, even without low-level optimization,
  137. will often run many times faster than a mediocre design with low-level
  138. optimization.
  139. Incremental design
  140. ~~~~~~~~~~~~~~~~~~
  141. Of course, in practice, unless you have prior knowledge, you are unlikely to
  142. come up with the best design the first time. Instead, you'll often make a series
  143. of versions of a particular area of code, each taking a different approach to
  144. the problem, until you come to a satisfactory solution. It's important not to
  145. spend too much time on the details at this stage until you have finalized the
  146. overall design. Otherwise, much of your work will be thrown out.
  147. It's difficult to give general guidelines for performant design because this is
  148. so dependent on the problem. One point worth mentioning though, on the CPU side,
  149. is that modern CPUs are nearly always limited by memory bandwidth. This has led
  150. to a resurgence in data-oriented design, which involves designing data
  151. structures and algorithms for *cache locality* of data and linear access, rather
  152. than jumping around in memory.
  153. The optimization process
  154. ~~~~~~~~~~~~~~~~~~~~~~~~
  155. Assuming we have a reasonable design, and taking our lessons from Knuth, our
  156. first step in optimization should be to identify the biggest bottlenecks - the
  157. slowest functions, the low-hanging fruit.
  158. Once we've successfully improved the speed of the slowest area, it may no
  159. longer be the bottleneck. So we should test/profile again and find the next
  160. bottleneck on which to focus.
  161. The process is thus:
  162. 1. Profile / Identify bottleneck.
  163. 2. Optimize bottleneck.
  164. 3. Return to step 1.
  165. Optimizing bottlenecks
  166. ~~~~~~~~~~~~~~~~~~~~~~
  167. Some profilers will even tell you which part of a function (which data accesses,
  168. calculations) are slowing things down.
  169. As with design, you should concentrate your efforts first on making sure the
  170. algorithms and data structures are the best they can be. Data access should be
  171. local (to make best use of CPU cache), and it can often be better to use compact
  172. storage of data (again, always profile to test results). Often, you precalculate
  173. heavy computations ahead of time. This can be done by performing the computation
  174. when loading a level, by loading a file containing precalculated data, or
  175. by storing the results of complex calculations into a script constant and
  176. reading its value.
  177. Once algorithms and data are good, you can often make small changes in routines
  178. which improve performance. For instance, you can move some calculations outside
  179. of loops or transform nested ``for`` loops into non-nested loops.
  180. (This should be feasible if you know a 2D array's width or height in advance.)
  181. Always retest your timing/bottlenecks after making each change. Some changes
  182. will increase speed, others may have a negative effect. Sometimes, a small
  183. positive effect will be outweighed by the negatives of more complex code, and
  184. you may choose to leave out that optimization.
  185. Appendix
  186. --------
  187. Bottleneck math
  188. ~~~~~~~~~~~~~~~
  189. The proverb *"a chain is only as strong as its weakest link"* applies directly to
  190. performance optimization. If your project is spending 90% of the time in
  191. function ``A``, then optimizing ``A`` can have a massive effect on performance.
  192. .. code-block:: none
  193. A: 9 ms
  194. Everything else: 1 ms
  195. Total frame time: 10 ms
  196. .. code-block:: none
  197. A: 1 ms
  198. Everything else: 1ms
  199. Total frame time: 2 ms
  200. In this example, improving this bottleneck ``A`` by a factor of 9× decreases
  201. overall frame time by 5× while increasing frames per second by 5×.
  202. However, if something else is running slowly and also bottlenecking your
  203. project, then the same improvement can lead to less dramatic gains:
  204. .. code-block:: none
  205. A: 9 ms
  206. Everything else: 50 ms
  207. Total frame time: 59 ms
  208. .. code-block:: none
  209. A: 1 ms
  210. Everything else: 50 ms
  211. Total frame time: 51 ms
  212. In this example, even though we have hugely optimized function ``A``,
  213. the actual gain in terms of frame rate is quite small.
  214. In games, things become even more complicated because the CPU and GPU run
  215. independently of one another. Your total frame time is determined by the slower
  216. of the two.
  217. .. code-block:: none
  218. CPU: 9 ms
  219. GPU: 50 ms
  220. Total frame time: 50 ms
  221. .. code-block:: none
  222. CPU: 1 ms
  223. GPU: 50 ms
  224. Total frame time: 50 ms
  225. In this example, we optimized the CPU hugely again, but the frame time didn't
  226. improve because we are GPU-bottlenecked.