Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve build times #1573

Open
DanRStevens opened this issue Feb 11, 2025 · 2 comments
Open

Improve build times #1573

DanRStevens opened this issue Feb 11, 2025 · 2 comments

Comments

@DanRStevens
Copy link
Member

DanRStevens commented Feb 11, 2025

Ideas taken from:


Commands from the above linked issues were used with GCC, Clang, and MSBuild to get build time reports.

Related idea, since the project is being split into sub projects, it may be possible to cache build outputs per sub project, which may help improve incremental build performance.


GCC:

make CXXFLAGS_EXTRA="-ftime-report"

GCC showed the vast majority of the time was spent parsing, with template instantiation a distant 2nd.


make CXX="clang++" CXXFLAGS_EXTRA="-ftime-report"

Clang results are a bit harder to read and draw conclusions from. Seems most of the time was spent on various passes and analysis, and also a good chunk on code generation.

It's also possible to include the -ftime-trace flag to get .json output alongside the object files:

make CXX="clang++" CXXFLAGS_EXTRA="-ftime-report -ftime-trace"

The output can be viewed using Chrome: chrome://tracing/
Drag and drop a trace file into Chrome, such as: .build/Debug_Linux_appOPHD/Intermediate/main.json

These flame charts are very useful for determining which header files are slow to #include and parse. Sometimes the results are surprising.

Image


There was an outdated note about getting build timings from MSVC using:

msbuild /fl /flp:Verbosity=diagnostic Your.sln

Documentation:
https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-command-line-reference?view=vs-2022

Of interest is the documentation for the -consoleLoggerParameters flag, which is related to the -fileLoggerParameters (-flp) flag.

It seems the Verbosity=diagnostic flag no longer displays timing information, though it does give a lot of other detailed information. Some timing information can be found using PerformanceSummary:

msbuild . /maxCpuCount /warnAsError /property:RunCodeAnalysis=true /consoleLoggerParameters:PerformanceSummary
  234 ms  RunMergeNativeCodeAnalysis                 7 calls
  235 ms  GetCopyToOutputDirectoryItems              7 calls
  313 ms  ClCompile                                  7 calls
 1188 ms  GetReferencedVCProjectsInfo                7 calls
 2955 ms  RunNativeCodeAnalysis                      7 calls
 4502 ms  Build                                      8 calls
20483 ms  ResolveProjectReferences                   7 calls
...
   62 ms  Touch                                     14 calls
  172 ms  MergeNativeCodeAnalysis                    7 calls
  187 ms  Link                                       4 calls
  219 ms  CallTarget                                14 calls
  297 ms  CL                                         7 calls
 2955 ms  NativeCodeAnalysis                         7 calls
26361 ms  MSBuild                                   26 calls

For MSVC there also appear to be some undocumented compiler (/Bt, /Bt+) and linker (/time, /time+) flags:
https://devblogs.microsoft.com/cppblog/vc-tip-get-detailed-build-throughput-diagnostics-using-msbuild-compiler-and-linker/

Similarly there is also an undocumented flag /d2cgsummary:
https://aras-p.info/blog/2017/10/23/Best-unknown-MSVC-flag-d2cgsummary/


Of these options, the -ftime-trace option for Clang, and the Chrome flame chart display at chrome://tracing/ appear to be the most promising tool for investigating and improving build times.

@DanRStevens
Copy link
Member Author

I was exploring the flame charts a bit. I noticed a number of standard library includes appeared to be kind of heavy:

  • <stdexcept> (mostly because of <string>)
  • <string>
  • <memory>

Unfortunately those are pretty standard, and pretty widespread. Still, it may be possible to move some dependencies out of header files and into source files, where they will have less of a transitive effect.

Of note, the <string_view> header takes about half the time needed by <string>. Though to be fair, <string> includes <string_view>, so it's going to take longer to parse.


I noticed a number of NAS2D headers appeared to be a bit heavy, though sometimes depending on what's already been included. For instance, if <string> is already included, then a NAS2D header using <string> will become less heavy of an import, since <string> won't need to be processed again.

Heavy includes:

  • <Signal.h> (because of <Delegate.h>)
  • <Delegate.h> (because of <memory>)
  • <Vector.h> (because of <string>, and also indirectly through <stdexcept>)
  • <Point.h> (depends on <Vector.h>)
  • <Rectangle.h> (depends on <Point.h> and <Vector.h>)
  • <Renderer.h> (depends on <chrono>, and <Signal.h>)
  • <Fade.h> (depends on <Signal.h> and <chrono>)

Medium includes:

  • <EventHandler.h> (slow to parse)

Notable includes of OPHD headers:

  • "ResearchFacility.h" (relies on <cmath>)
  • "UiConstants.h" (relies on <chrono>)
  • "StringTable.h" (relies on <Renderer.h>)
  • "Structure.h" (relies on "StringTable.h")
  • "Control.h" (relies on <Signal.h>)
  • "XmlSerializer" (mostly because of <unordered_map>)
  • "StorableResource.h" (not very heavy, a few includes, parsing, template instantiations for std::vector functions)
  • "Constants/Strings.h" (doesn't depend on much, just a bit slow)
  • "Cache.h" (depends on multiple files, slowest is <memory>)
  • "Structure.h" (depends on "MapObject.h" and "StringTable.h")
  • "MapObject.h" (depends on <Signal.h>)
  • "MapViewState.h" (big and slow)

One of the most notable includes was the widespread use of Signal.h. Given that we never use more than a single listener, we should maybe consider an alternate approach. Perhaps there is a lighter solution using lambdas. Or perhaps using std::function from <functional> would be appropriate. The <functional> header is maybe a little bit heavy, but much less so than <Signal.h>, with less than half the processing time.

@DanRStevens
Copy link
Member Author

Did a little more exploring with Clang's -ftime-trace JSON files.

To get a list of includes, and the duration needed to process them:

jq -r '[.traceEvents[] | select(.name=="Source") | {dur, path: .args.detail}] | sort_by(.dur) | .[] | join(" ")' .build/Debug_Linux_appOPHD/Intermediate/main.json

The paths to standard library includes are not canonical, such as:
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/memory

realpath /usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/memory

/usr/include/c++/12/memory

Unfortunately realpath doesn't seem to allow paths to be piped through it on stdin. It only seems to accept command line arguments.

The paths are consistent with the path structure though, so a single sed expression can be used to correct them:

echo "/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/memory" | sed -r 's+bin/\.\./lib/gcc/x86_64-linux-gnu/12/\.\./\.\./\.\./\.\./++g'

/usr/include/c++/12/memory

Combining that into one pipe:

jq -r '[.traceEvents[] | select(.name=="Source") | {dur, path: .args.detail}] | sort_by(.dur) | .[] | join(" ")' .build/Debug_Linux_appOPHD/Intermediate/main.json | sed -r 's+bin/\.\./lib/gcc/x86_64-linux-gnu/12/\.\./\.\./\.\./\.\./++g'

Potentially useful, is getting a list of all the standard library includes used directly by the project:

grep -rh "#include <[^/]*>" appOPHD/ | sed -e 's/^[ \t]*//' | sort | uniq

Note that we identify standard library includes here by the absence of a / in the include path. For 3rd party libraries, there is generally a project folder name prefix to the paths.

This output differs from the jq commands above in that it only shows directly included headers. It does not list headers that are transitively included from the standard library headers.

The sed portion of the pipe was used to strip leading spaces.

For non-standard library includes, there is usually a "projectName/" prefix to the include. Hence non-standard library includes can be found with:

grep -rh "#include <.*/.*>" appOPHD/ | sed -e 's/^[ \t]*//' | sort | uniq

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant