Skip to content

Commit a357a9f

Browse files
committed
Add 'high level / low level' and 'tools' sections to the optimization article.
1 parent 39b1cd1 commit a357a9f

File tree

1 file changed

+99
-10
lines changed

1 file changed

+99
-10
lines changed

engine/guidelines/optimization.rst

Lines changed: 99 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,13 @@ Choosing what to optimize
2020
-------------------------
2121

2222
Predicting which code would benefit from optimization can be difficult without
23-
using performance analysis tools.
23+
using performance analysis `tools <#tools-for-optimization>`_.
2424

2525
Oftentimes code that looks slow has no impact on overall performance, and code
2626
that looks like it should be fast has a huge impact on performance. Further,
2727
reasoning about why a certain chunk of code is slow is often impossible to do
2828
without detailed metrics (e.g. from a profiler).
2929

30-
Instructions on using some common profilers with Godot can be found `here
31-
<https://docs.godotengine.org/en/stable/engine_details/development/debugging/using_cpp_profilers.html>`_.
32-
3330
As an example, you may optimize a chunk of code by caching intermediate values.
3431
However, if that code was slow due to memory constraints, caching the values and
3532
reading them later may be even slower than calculating them from scratch!
@@ -96,6 +93,89 @@ Once you have your baseline profile/benchmark, make your changes and rebuild the
9693
engine with the exact same build settings you used before. Then profile again
9794
and compare the results.
9895

96+
High level vs low level optimization
97+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
98+
99+
Optimizing code is different between "high level" and "low level" code.
100+
101+
"High level" code refers to code that heavily relies on frameworks and functions to
102+
perform its task. This is most of Godot's code. In high level code, it is often most
103+
important to avoid doing expensive work entirely. For example, by caching values
104+
rather than making duplicate calls, by avoiding copying data unnecessarily, or by
105+
replacing calls to expensive functions with calls to cheap functions.
106+
107+
In contrast, "low level" code refers to code that is working mostly with C++ language
108+
features, such as primitive types and ``for``-loops. Optimizing low level code, often
109+
referred to as "micro-optimization", can be more difficult, because it requires intimate
110+
knowledge about C++ compiler intrinsics, as well as the inner workings of the CPU and RAM.
111+
Furthermore, improving low level code is often unintuitive, and can reduce its readability
112+
or robustness. We recommend against attempting to optimize low level code, unless you are
113+
a very experienced low level C++ programmer.
114+
115+
.. note:::
116+
117+
For micro-optimizations, C++ compilers will often be aware of basic tricks and
118+
will already perform them in optimized builds. Therefore, not all changes that
119+
look like they should optimize the code will actually make the code faster.
120+
121+
Tools for optimization
122+
~~~~~~~~~~~~~~~~~~~~~~
123+
124+
Profilers
125+
^^^^^^^^^
126+
127+
Profilers are the most important tool for everyone optimizing code.
128+
They show you which parts of the code are responsible for slow execution or heavy CPU load,
129+
and are therefore excellent for identifying what needs to be optimized. Profilers can
130+
also be used to identify whether the problem has been resolved, by profiling again after
131+
making the changes. Godot has a built-in profiler, but it does not provide very detailed
132+
information. Instead, use dedicated C++ profilers, which are
133+
`explained in the Godot documentation <https://docs.godotengine.org/en/stable/engine_details/development/debugging/using_cpp_profilers.html>`__.
134+
135+
Benchmarks
136+
^^^^^^^^^^
137+
138+
Benchmarks can be a great and simple tool to test the impact of your changes
139+
of an isolated piece of code. However, benchmarks can be deceptive: It's easy to
140+
accidentally write a benchmark that highlights a way in which performance was
141+
improved, while ignoring other ways in which it was made worse.
142+
143+
To give one example: The most expensive operation of modern CPU programming is fetching RAM
144+
that is not in the cache. Benchmarks often test code with values that are already in the cache
145+
("hot" execution), but often, it is more important to optimize for the case where values are not
146+
in the cache yet ("cold" execution).
147+
148+
Another common source of confusion is compiler optimization: One might write a benchmark that
149+
looks like it should test the code faithfully, but the benchmarks show no improvement. This might
150+
be indicative of a poorly written benchmark, which the compiler is able to "optimize away" by using
151+
`constant folding <https://en.wikipedia.org/wiki/Constant_folding>`__ and other tricks.
152+
For these, and other reasons, it is difficult to write good benchmarks. When using benchmarks to
153+
test the performance of your code, always be aware of its potential caveats, and try to familiarize
154+
yourself with good benchmark practices.
155+
156+
To start writing benchmarks in Godot, use the following GDScript code template:
157+
158+
.. code-block:: gdscript
159+
160+
var start = Time.get_ticks_msec()
161+
var s := "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
162+
for i in range(10000):
163+
s.replace("e", "b") # Benchmarks the 'replace' function.
164+
print(Time.get_ticks_msec() - start, "ms")
165+
166+
Alternatively, you can benchmark right from C++:
167+
168+
.. code-block:: cpp
169+
170+
String s = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
171+
172+
auto t0 = std::chrono::high_resolution_clock::now();
173+
for (int i = 0; i < 100000; i ++) {
174+
String s1 = s.replace("e", "b"); // Benchmarks the 'replace' function.
175+
}
176+
auto t1 = std::chrono::high_resolution_clock::now();
177+
std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count() << "ms\n";
178+
99179
.. note::
100180

101181
Results will fluctuate, so you'll need to make your test project or
@@ -104,20 +184,29 @@ and compare the results.
104184
test multiple times, and observe how much the results fluctuate. Fluctuations of up
105185
to 10% are common and expected. The fastest run is usually the most accurate number.
106186

187+
Assembly viewers
188+
^^^^^^^^^^^^^^^^
189+
190+
Assembly viewers show the final compiled version of your code in a readable
191+
format called assembly. Examining the assembly can be an effective way to
192+
optimize low level code. It is not effective for optimization of high level
193+
code, and should often be the "last resort", when it is clear that other
194+
optimization tools are not applicable. Effectively working with assembly to
195+
optimize code requires an intimate understanding of the cost of individual
196+
instructions. Agner Fog's `software optimization resources <https://www.agner.org/optimize/>`__
197+
are invaluable for this, especially his `C++ optimization guide <https://agner.org/optimize/optimizing_cpp.pdf>`__.
198+
To view assembly, you either use an assembly viewer program for desktop, or write dedicated
199+
functions in the popular multi-architecture tool `Compiler Explorer <https://godbolt.org>`__.
200+
107201
Pull request requirements
108202
-------------------------
109203

110204
When making an optimization PR you should:
111205

112206
- Explain why you chose to optimize this code (e.g. include the profiling result, link the issue report, etc.).
113207
- Show that you improved the code either by profiling again, or running systematic benchmarks.
208+
See `tools <#tools-for-optimization>`__ for more info.
114209
- Test on multiple platforms where appropriate, especially mobile.
115-
- When micro-optimizing, show assembly before / after where appropriate.
116-
117-
In particular, you should be aware that for micro-optimizations, C++ compilers will often
118-
be aware of basic tricks and will already perform them in optimized builds. This is why
119-
showing before / after assembly can be important in these cases.
120-
(`godbolt <https://godbolt.org/>`_ can be particularly useful for this purpose.)
121210

122211
The most important point to get across in your PR is to highlight the source of
123212
the performance issues, and have a clear explanation for how your PR fixes that

0 commit comments

Comments
 (0)