• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Build Performance
2
3## Debugging Build Performance
4
5### Tracing
6
7soong_ui has tracing built in, so that every build execution's trace can be
8viewed.  Just open `$OUT_DIR/build.trace.gz` in Chrome's <chrome://tracing>, or
9with [catapult's trace viewer][catapult trace_viewer]. The last few traces are
10stored in `build.trace.#.gz` (larger numbers are older). The associated logs
11are stored in `soong.#.log` and `verbose.#.log.gz`.
12
13![trace example](./trace_example.png)
14
15### Soong
16
17Soong can be traced and profiled using the standard Go tools. It understands
18the `-cpuprofile`, `-trace`, and `-memprofile` command line arguments, but we
19don't currently have an easy way to enable them in the context of a full build.
20
21### Kati
22
23In general, the slow path of reading Android.mk files isn't particularly
24performance sensitive, since it doesn't need to happen on every build. It is
25important for the fast-path (detecting whether it needs to regenerate the ninja
26file) to be fast however. And it shouldn't hit the slow path too often -- so
27don't rely on output of a `$(shell)` command that includes the current timestamp,
28or read a file that's going to change on every build.
29
30#### Regen check is slow
31
32In most cases, we've found that the fast-path is slow because all of the
33`$(shell)` commands need to be re-executed to determine if their output changed.
34The `$OUT_DIR/verbose.log.gz` contains statistics from the regen check:
35
36```
37verbose: *kati*: regen check time: 0.754030
38verbose: *kati*: glob time (regen): 0.545859 / 43840
39verbose: *kati*: shell time (regen): 0.278095 / 66 (59 unique)
40verbose: *kati*:   0.012 / 1 mkdir -p out/target/product/generic && echo Android/aosp_arm/generic:R/AOSP.MASTER/$(date -d @$(cat out/build_date.txt) +%m%d%H%M):eng/test-keys >out/target/product/generic/build_fingerprint.txt && grep " " out/target/product/generic/build_fingerprint.txt
41verbose: *kati*:   0.010 / 1 echo 'com.android.launcher3.config.FlagOverrideSampleTest com.android.launcher3.logging.FileLogTest com.android.launcher3.model.AddWorkspaceItemsTaskTest com.android.launcher3.model.CacheDataUpdatedTaskTest com.android.launcher3.model.DbDowngradeHelperTest com.android.launcher3.model.GridBackupTableTest com.android.launcher3.model.GridSizeMigrationTaskTest com.android.launcher3.model.PackageInstallStateChangedTaskTest com.android.launcher3.popup.PopupPopulatorTest com.android.launcher3.util.GridOccupancyTest com.android.launcher3.util.IntSetTest' | tr ' ' '\n' | cat
42verbose: *kati*:   0.010 / 1 cd cts/tests/framework/base/windowmanager ; find -L  * -name "Components.java" -and -not -name ".*"
43verbose: *kati*:   0.010 / 1 git -C test/framework/build log -s -n 1 --format="%cd" --date=format:"%Y%m%d_%H%M%S" 2>/dev/null
44verbose: *kati*:   0.009 / 2 cd development/samples/ShortcutDemo/publisher ; find -L  ../common/src -name "*.java" -and -not -name ".*"
45verbose: *kati*:   0.009 / 2 cd development/samples/ShortcutDemo/launcher ; find -L  ../common/src -name "*.java" -and -not -name ".*"
46verbose: *kati*:   0.009 / 1 if ! cmp -s out/target/product/generic/obj/CONFIG/kati_packaging/dist.mk.tmp out/target/product/generic/obj/CONFIG/kati_packaging/dist.mk; then mv out/target/product/generic/obj/CONFIG/kati_packaging/dist.mk.tmp out/target/product/generic/obj/CONFIG/kati_packaging/dist.mk; else rm out/target/product/generic/obj/CONFIG/kati_packaging/dist.mk.tmp; fi
47verbose: *kati*:   0.008 / 1 mkdir -p out/target/product/generic && echo R/AOSP.MASTER/$(cat out/build_number.txt):eng/test-keys >out/target/product/generic/build_thumbprint.txt && grep " " out/target/product/generic/build_thumbprint.txt
48verbose: *kati*:   0.007 / 1 echo 'com.android.customization.model.clock.BaseClockManagerTest com.android.customization.model.clock.ClockManagerTest com.android.customization.model.grid.GridOptionsManagerTest com.android.customization.model.theme.ThemeManagerTest' | tr ' ' '\n' | cat
49verbose: *kati*:   0.007 / 1 uname -sm
50verbose: *kati*: stat time (regen): 0.361907 / 1241
51```
52
53In this case, the total time spent checking was 0.75 seconds, even though the
54other "(regen)" numbers add up to more than that (some parts are parallelized
55where possible). Often times, the biggest contributor is the `$(shell)` times
56-- in this case, 66 calls took 0.27s. The top 10 longest shell functions are
57printed.
58
59All the longest commands in this case are all variants of a call to `find`, but
60this is where using pure make functions instead of calling out to the shell can
61make a performance impact -- many calls to check if `26 > 20` can add up. We've
62added some basic math functions in `math.mk` to help some common use cases that
63used to be rather expensive when they were used too often.
64
65There are some optimizations in place for find commands -- if Kati can
66understand the find command, the built-in find emulator can turn some of them
67into glob or stat checks (falling back to calling `find` if one of those imply
68that the output may change). Many of the common macros produce find commands
69that Kati can understand, but if you're writing your own, you may want to
70experiment with other options if they're showing up in this list. For example,
71if this was significantly more expensive (either in runtime, or was called
72often):
73
74```
75.../kati.go:127: *kati*:  0.015 cd libcore && (find luni/src/test/java -name "*.java" 2> /dev/null) | grep -v -f java_tests_blacklist
76```
77
78It may be more efficient to move the grep into make, so that the `find` portion
79can be rewritten and cached:
80
81```
82$(filter-out $(file <$(LOCAL_PATH)/java_tests_blacklist),$(call all-java-files-under,luni/src/test/java))
83```
84
85Others can be simplified by just switching to an equivalent find command that
86Kati understands:
87
88```
89.../kati.go:127: *kati*:  0.217 find device vendor -type f -name \*.pk8 -o -name verifiedboot\* -o -name \*.x509.pem -o -name oem\*.prop | sort
90```
91
92By adding the implicit `-a` and moving the `| sort` to Make, this can now be
93cached by Kati:
94
95```
96$(sort $(shell find device vendor -type -f -a -name \*.pk8 -o -name verifiedboot\* -o -name \*.x509.pem -o -name oem\*.prop))
97```
98
99Kati has now learned about the implicit `-a`, so this particular change is no
100longer necessary, but the basic concept holds.
101
102#### Kati regens too often
103
104Kati prints out what triggered the slow path to be taken -- this can be a
105changed file, a changed environment variable, or different output from a
106`$(shell)` command:
107
108```
109out/soong/Android-aosp_arm.mk was modified, regenerating...
110```
111
112The state is stored in `$OUT_DIR/.kati_stamp*` files, and can be (partially)
113read with the `ckati_stamp_dump` tool in prebuilts/build-tools. More debugging
114is available when ckati is run with `--regen_debug`, but that can be a lot of
115data to understand.
116
117#### Debugging the slow path
118
119Kati will now dump out information about which Makefiles took the most time to
120execute. This is also in the `verbose.log.gz` file:
121
122```
123verbose: *kati*: included makefiles: 73.640833 / 232810 (1066 unique)
124verbose: *kati*:  18.389 /     1 out/soong/Android-aosp_arm.mk
125verbose: *kati*:  13.137 / 20144 build/make/core/soong_cc_prebuilt.mk
126verbose: *kati*:  11.743 / 27666 build/make/core/base_rules.mk
127verbose: *kati*:   2.289 /     1 art/Android.mk
128verbose: *kati*:   2.054 /     1 art/build/Android.cpplint.mk
129verbose: *kati*:   1.955 / 28269 build/make/core/clear_vars.mk
130verbose: *kati*:   1.795 /   283 build/make/core/package.mk
131verbose: *kati*:   1.790 /   283 build/make/core/package_internal.mk
132verbose: *kati*:   1.757 / 17382 build/make/core/link_type.mk
133verbose: *kati*:   1.078 /   297 build/make/core/aapt2.mk
134```
135
136This shows that soong_cc_prebuilt.mk was included 20144 times, for a total time
137spent of 13.137 secounds. While Android-aosp_arm.mk was only included once, and
138took 18.389 seconds. In this case, Android-aosp_arm.mk is the only file that
139includes soong_cc_prebuilt.mk, so we can safely assume that 13 of the 18 seconds
140in Android-aosp_arm.mk was actually spent within soong_cc_prebuilt.mk (or
141something that it included, like base_rules.mk).
142
143By default this only includes the top 10 entries, but you can ask for the stats
144for any makefile to be printed with `$(KATI_profile_makefile)`:
145
146```
147$(KATI_profile_makefile build/make/core/product.mk)
148```
149
150With these primitives, it's possible to get the timing information for small
151chunks, or even single lines, of a makefile. Just move the lines you want to
152measure into a new makefile, and replace their use with an `include` of the
153new makefile. It's possible to analyze where the time is being spent by doing
154a binary search using this method, but you do need to be careful not to split
155conditionals across two files (the ifeq/else/endif must all be in the same file).
156
157### Ninja
158
159#### Understanding why something rebuilt
160
161Add `NINJA_ARGS="-d explain"` to your environment before a build, this will cause
162ninja to print out explanations on why actions were taken. Start reading from the
163beginning, as this much data can be hard to read:
164
165```
166$ cd art
167$ mma
168$ touch runtime/jit/profile_compilation_info.h
169$ NINJA_ARGS="-d explain" mma
170...
171ninja explain: output out/soong/.intermediates/art/tools/cpp-define-generator/cpp-define-generator-data/linux_glibc_x86_64/obj/art/tools/cpp-define-generator/main.o older than most recent input art/runtime/jit/profile_compilation_info.h (
1721516683538 vs 1516685188)
173ninja explain: out/soong/.intermediates/art/tools/cpp-define-generator/cpp-define-generator-data/linux_glibc_x86_64/obj/art/tools/cpp-define-generator/main.o is dirty
174ninja explain: out/soong/.intermediates/art/tools/cpp-define-generator/cpp-define-generator-data/linux_glibc_x86_64/cpp-define-generator-data is dirty
175ninja explain: out/soong/host/linux-x86/bin/cpp-define-generator-data is dirty
176ninja explain: out/soong/.intermediates/art/tools/cpp-define-generator/cpp-define-generator-asm-support/gen/asm_support_gen.h is dirty
177ninja explain: out/soong/.intermediates/art/cmdline/art_cmdline_tests/android_arm_armv7-a_core_cmdline_parser_test/obj/art/cmdline/cmdline_parser_test.o is dirty
178...
179```
180
181In this case, art/cmdline/cmdline_parser_test.o was rebuilt because it uses
182asm_support_gen.h, which was generated by cpp-define-generator-data, which uses
183profile_compilation_info.h.
184
185You'll likely need to cross-reference this data against the build graph in the
186various .ninja files. The files are (mostly) human-readable, but a (slow) web
187interface can be used by running `NINJA_ARGS="-t browse <target>" m`.
188
189#### Builds take a long time
190
191If the long part in the trace view of a build is a relatively solid block, then
192the performance is probably more related to how much time the actual build
193commands are taking than having extra dependencies, or slowdowns in
194soong/kati/ninja themselves.
195
196Beyond looking at visible outliers in the trace view, we don't have any tooling
197to help in this area yet. It's possible to aggregate some of the raw data
198together, but since our builds are heavily parallelized, it's particularly easy
199for build commands to impact unrelated build commands. This is an area we'd
200like to improve -- we expect keeping track of user/system time per-action would
201provide more reliable data, but tracking some full-system data (memory/swap
202use, disk bandwidth, etc) may also be necessary.
203
204## Known Issues
205
206### Common
207
208### <= Android 10 (Q): mm
209
210Soong always loads the entire module graph, so as modules convert from Make to
211Soong, `mm` is becoming closer to `mma`. This produces more correct builds, but
212does slow down builds, as we need to verify/produce/load a larger build graph.
213
214As of Android Q, loading large build graphs is fast, and in Android R, `mm` is
215now an alias of `mma`.
216
217### Android 8.1 (Oreo MR1)
218
219In some cases, a tree would get into a state where Soong would be run twice on
220every incremental build, even if there was nothing to do. This was fixed in
221master with [these changes][blueprint_microfactory], but they were too
222significant to backport at the time. And while they fix this particular issue,
223they appear to cause ninja to spend more time during every build loading the
224`.ninja_log` / `.ninja_deps` files, especially as they become larger.
225
226A workaround to get out of this state is to remove the build.ninja entry from
227`$OUT_DIR/.ninja_log`:
228
229```
230sed -i "/\/build.ninja/d" $(get_build_var OUT_DIR)/.ninja_log
231```
232
233[catapult trace_viewer]: https://github.com/catapult-project/catapult/blob/master/tracing/README.md
234[ninja parse optimization]: https://android-review.googlesource.com/c/platform/external/ninja/+/461005
235[blueprint_microfactory]: https://android-review.googlesource.com/q/topic:%22blueprint_microfactory%22+status:merged
236