Difference between revisions of "CMake Performance Tips"

From KitwarePublic
Jump to navigationJump to search
(Mention dummy functions conditional switch)
(Correct sections (asleep at the wheel...), add sorting comment)
Line 1: Line 1:
While CMake itself is already very fast, there are some tuning things you can do to ensure it works
While CMake itself is already very fast, there are some tuning things you can do to ensure it works
as fast as possible.
as fast as possible.
This list is supposed to be sorted (ordered) according to relevance (severity, amount of savings). TODO: do that sorting.


=CMake build time=
=Build CMake binary with optimization enabled=
 
==Build it with optimization enabled==


Ok, this is obvious, but anyway. Let's say you build CMake yourself without any special settings, e.g.
Ok, this is obvious, but anyway. Let's say you build CMake yourself without any special settings, e.g.
Line 40: Line 39:
CMake built with optimizations enabled can give you an almost 50% performance boost (time for running
CMake built with optimizations enabled can give you an almost 50% performance boost (time for running
CMake on VTK went down from 25 s to 14 s).
CMake on VTK went down from 25 s to 14 s).
=CMake configure time=


==Use LIST(APPEND ...)==
==Use LIST(APPEND ...)==
Line 56: Line 57:


LIST(APPEND ...) is for large lists and appends much faster than using SET().
LIST(APPEND ...) is for large lists and appends much faster than using SET().
=CMake configure time=


==Reduce add_custom_command()s DEPENDS lists==
==Reduce add_custom_command()s DEPENDS lists==

Revision as of 05:47, 23 August 2012

While CMake itself is already very fast, there are some tuning things you can do to ensure it works as fast as possible. This list is supposed to be sorted (ordered) according to relevance (severity, amount of savings). TODO: do that sorting.

Build CMake binary with optimization enabled

Ok, this is obvious, but anyway. Let's say you build CMake yourself without any special settings, e.g.

$ cmake ..
$ make

If you do it this way, you will get a CMake with optimizations turned off. There are different ways to get an optimized build. You can select one of the predefined build types:

$ cmake -DCMAKE_BUILD_TYPE=RELEASE ..
$ make

Also possible are RELWITHDEBINFO and MINSIZEREL.

or

$ export CXXFLAGS=-O2
$ cmake ..
$ make

or

$ export CXXFLAGS=-O2
$ cmake ..
$ make edit_cache (or ccmake ..)
... edit CMAKE_CXX_FLAGS in the advanced view
$ make

CMake built with optimizations enabled can give you an almost 50% performance boost (time for running CMake on VTK went down from 25 s to 14 s).

CMake configure time

Use LIST(APPEND ...)

There are two ways to append values to a variable in CMake:

  SET(myVar ${myVar} newItem)

and since CMake 2.4 there is the new LIST() command:

  LIST(APPEND myVar newItem)

LIST(APPEND ...) is for large lists and appends much faster than using SET().

Reduce add_custom_command()s DEPENDS lists

If your build setup happens to contain many targets which all depend on the same sizeable list of file dependencies, then it might be useful to establish one single custom command (plus its associated target) which DEPENDS on those many files and creates one single OUTPUT "stamp file" ("one of the files changed" watchdog file) which can then be DEPENDS-fed into all affected add_custom_command()s as a single file dependency. A very nice way to figure out whether this applies to your build environment is to do:

ninja -t graph > /tmp/graphviz.log
dot -Tsvg /tmp/graphviz.log >/tmp/cmake_ninja.svg

and watch the resulting graph monstrosity in awe :)

Use an include guard

For CMake modules (files referenced via include() statement), you could use something like:

if(my_module_xyz_included)
  return()
endif(my_module_xyz_included)
set(my_module_xyz_included true)

at the beginning of your module file, to avoid repeated parsing within sibling scopes (sub directories, etc.), which also cuts down on amount of

cmake --trace

log traffic.

Conditional find_package()

Some other part may already have queried this package and thus caused the corresponding CACHE variable to have been set. find_package() is quite expensive, and AFAIK this yields some nice speedup. This might be questionable, though, in case of changing requirements/requested configurations between project units (but in that case you'd probably have a conflict anyway since there's only a single CACHE variable involved).

if(NOT xyz_EXECUTABLE)
  find_package(xyz REQUIRED)
endif(NOT xyz_EXECUTABLE)

Split modules into functions/definitions

As a general hint, it might be useful to split module files into containing either clean stateless non-specific (generic) helper functions or content which defines specific settings and calls some helper functions.

Conditional switching of (dummy) methods

Rather than doing a more costly

function(foobar)
  if(have_feature)
    if(is_ok)
...
endfunction(foobar)

it may be useful to make use of such a conditional to do a whole-function switch instead:

if(have_feature)
  function(foobar)
    (large implementation here)
  endfunction(foobar)
else(have_feature)
  function(foobar)
    # DUMMY
  endfunction(foobar)
endif(have_feature)

, thereby saving on function execution time in case of many repeated invocations. Note that this obviously comes with a static/dynamic tradeoff, however: While the conditional evaluation sitting within the function can obviously react on later changes to the conditional (have_feature in this case), the static-switch method is a one-time decision only.

Loop optimizations

Use these tricks to do an initial match query over the entire list prior to iterating over each element, and return() ASAP. I did not profile it whether these tricks are indeed faster, but for large lists it should be useful.

if("${list}" MATCHES ${elem_query}) # shortcut :)
  foreach(elem ${list})
    if(${elem} STREQUAL ${elem_query})
      set(elem_found true)
      return()/break() # don't forget these...
    endif(${elem} STREQUAL ${elem_query})
  endforeach(elem ${list})
endif("${list}" MATCHES ${elem_query})