cmake: compilation statistics per transation unit
Asked Answered
Q

5

33

I need to figure out which translation units need to be restructured to improve compile times, How do I get hold of the compilation time, using cmake, for my translation units ?

Quits answered 11/5, 2011 at 9:47 Comment(4)
As I always think cmake only generate conf files for native build systems (make, nmake, etc.). So cmake isn't actually involved into compile/link process. Perhaps you should look for such options into your native build system.Bisulcate
Well it is quite a common use-case. However, if you know how to do it for the standard linux make, that would be most helpfull.Quits
Unfortunately, I am not aware about any make options to accomplish this. The first solution that comes into my mind is to modify Makefiles to invoke time for each gcc command.Bisulcate
thanks beduin, a little bit more and you could have finished it of as an answer :DQuits
L
11

I would expect to replace the compiler (and/or linker) with 'time original-cmd'. Using plain 'make', I'd say:

make CC="time gcc"

The 'time' program would run the command and report on the time it took. The equivalent mechanism would work with 'cmake'. If you need to capture the command as well as the time, then you can write your own command analogous to time (a shell script would do) that records the data you want in the way you want.

Languedoc answered 12/5, 2011 at 5:9 Comment(3)
Usually time is built-in command. To use verbose mode one need to type make CC="/usr/bin/time -v gcc" command.Verena
You cannot pass the compiler this way for a CMake-generated make file. In general it does not work.Leviticus
@Leviticus I never saw any need or use to write Makefiles, which generate Makefiles. Potentially one could insert another layer of indirection?Dialogize
M
47

Following properties could be used to time compiler and linker invocations:

Those properties could be set globally, per directory and per target. That way you can only have a subset of your targets (say tests) to be impacted by this property. Also you can have different "launchers" for each target that also could be useful.

Keep in mind, that using "time" directly is not portable, because this utility is not available on all platforms supported by CMake. However, CMake provides "time" functionality in its command-line tool mode. For example:

# Set global property (all targets are impacted)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time")
# Set property for my_target only
set_property(TARGET my_target PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time")

Example CMake output:

[ 65%] Built target my_target
[ 67%] Linking C executable my_target
Elapsed time: 0 s. (time), 0.000672 s. (clock)

Note, that as of CMake 3.4 only Makefile and Ninja generators support this properties.

Also note, that as of CMake 3.4 cmake -E time has problems with spaces inside arguments. For example:

cmake -E time cmake "-GUnix Makefiles"

will be interpreted as:

cmake -E time cmake "-GUnix" "Makefiles"

I submitted patch that fixes this problem.

Mini answered 19/1, 2016 at 22:37 Comment(6)
The accepted answer did'nt work for me, but this one did.Shad
This has another problem: ${CMAKE_COMMAND} has spaces on windows and won't work as suggestedMyrtia
@Pavel you can add quotes: set_property(TARGET my_target PROPERTY RULE_LAUNCH_COMPILE "\"${CMAKE_COMMAND}\" -E time")Dalmatia
I am trying to use this for calling my own script (which should give start/end timestamps in epoch) but for some reason it does not work. (using time it does). set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E ${CMAKE_CURRENT_SOURCE_DIR}/myscript") My script looks just like that #!/bin/sh $* fails with: CMake Error: cmake version 3.14.5 Usage: /usr/bin/cmake -E <command> [arguments...] Available commands: ... (help) Any ideas?Cushitic
This works good unless the build goes in multiple threads, then it becomes unclear what is the relationship between file name and time, because they are mixed from all threads. Is it possible to solve somehow?Waitabit
@Waitabit output it to a file: You can do something like set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "time -f '%C %U %S' -a -o timings.txt") and that will append (-a) all the data to the output file (-o). Look up time --help for the format string, but it is Command User Time and System Time for my exampleDeaden
L
11

I would expect to replace the compiler (and/or linker) with 'time original-cmd'. Using plain 'make', I'd say:

make CC="time gcc"

The 'time' program would run the command and report on the time it took. The equivalent mechanism would work with 'cmake'. If you need to capture the command as well as the time, then you can write your own command analogous to time (a shell script would do) that records the data you want in the way you want.

Languedoc answered 12/5, 2011 at 5:9 Comment(3)
Usually time is built-in command. To use verbose mode one need to type make CC="/usr/bin/time -v gcc" command.Verena
You cannot pass the compiler this way for a CMake-generated make file. In general it does not work.Leviticus
@Leviticus I never saw any need or use to write Makefiles, which generate Makefiles. Potentially one could insert another layer of indirection?Dialogize
Y
8

To expand on the previous answer, here's a concrete solution that I just wrote up — which is to say, it definitely works in practice, not just in theory, but it has been used by only one person for approximately three minutes, so it probably has some infelicities.

#!/bin/bash
{ time clang "$@"; } 2> >(cat <(echo "clang $@") - >> /tmp/results.txt)

I put the above two lines in /tmp/time-clang and then ran

chmod +x /tmp/time-clang
cmake .. -DCMAKE_C_COMPILER=/tmp/time-clang
make

You can use -DCMAKE_CXX_COMPILER= to hook the C++ compiler in exactly the same way.

  • I didn't use make -j8 because I didn't want the results to get interleaved in weird ways.

  • I had to put an explicit hashbang #!/bin/bash on my script because the default shell (dash, I think?) on Ubuntu 12.04 wasn't happy with those redirection operators.

Yeeyegg answered 27/10, 2015 at 0:8 Comment(1)
This works great for me, thanks! However, I'm trying (and struggling) to pull out only the real output from time, and print it along with the clang command on a single line to results.txt?Musser
B
1

I think that the best option is to use:

set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "time -v")
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "time -v")

Despite what has been said above:

Keep in mind, that using "time" directly is not portable, because this utility is not available on all platforms supported by CMake. However, CMake provides "time"... https://mcmap.net/q/441754/-cmake-compilation-statistics-per-transation-unit

If your system contain it, you will get much better results with the -v flag.

e.g.

time -v  /usr/bin/c++     CMakeFiles/basic_ex.dir/main.cpp.o  -o basic_ex 
    Command being timed: "/usr/bin/c++ CMakeFiles/basic_ex.dir/main.cpp.o -o basic_ex"
    User time (seconds): 0.07
    System time (seconds): 0.01
    Percent of CPU this job got: 33%
    Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.26
    Average shared text size (kbytes): 0
    Average unshared data size (kbytes): 0
    Average stack size (kbytes): 0
    Average total size (kbytes): 0
    Maximum resident set size (kbytes): 16920
    Average resident set size (kbytes): 0
    Major (requiring I/O) page faults: 0
    Minor (reclaiming a frame) page faults: 6237
    Voluntary context switches: 7
    Involuntary context switches: 23
    Swaps: 0
    File system inputs: 0
    File system outputs: 48
    Socket messages sent: 0
    Socket messages received: 0
    Signals delivered: 0
    Page size (bytes): 4096
    Exit status: 0
Balaklava answered 8/4, 2021 at 21:33 Comment(0)
A
0

I couldn't make the custom step do anything sensible, but for collecting compile and link times I found the following useful.

Near the start of the top-level CMakeLists.txt:

set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${PROJECT_SOURCE_DIR}/timecompile")
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${PROJECT_SOURCE_DIR}/timelink")

Where timecompile is

#!/bin/bash

filename=[unknown]

i=1
while [ ${i} -le $# ]
do
  eval x='$'{${i}}
  if [ "$x" == "-c" ]
  then
    i=$((i + 1))
    eval filename='$'{${i}}
    break
  fi
  i=$((i + 1))
done

set -o pipefail
{ cmake -E time "$@"; } | awk "{print \"Compile time (s) ${filename}: \" \$4}"

and timelink is

#!/bin/bash

if [ "`basename $1`" == "ar" -o "`basename $1`" == "ranlib" ]
then
  filename="$1 $2"
else
  filename=[unknown]
fi

i=1
while [ ${i} -le $# ]
do
  eval x='$'{${i}}
  if [ "$x" == "-o" ]
  then
    i=$((i + 1))
    eval filename='$'{${i}}
    break
  fi
  i=$((i + 1))
done

if [ "${filename}" == "[unknown]" ]
then
  echo "[unknown] link line: $@"
fi

set -o pipefail
{ cmake -E time "$@"; } | awk "{print \"Link time (s) ${filename}: \" \$4}"

Inspired by https://stackoverflow.com/a/33357776.

Alcaide answered 18/6 at 12:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.