1 #! /usr/bin/env python3
3 # Copyright (C) 2010-2020 Joel Rosdahl and other contributors
5 # See doc/AUTHORS.adoc for a complete list of contributors.
7 # This program is free software; you can redistribute it and/or modify it under
8 # the terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 3 of the License, or (at your option) any later
12 # This program is distributed in the hope that it will be useful, but WITHOUT
13 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 # You should have received a copy of the GNU General Public License along with
18 # this program; if not, write to the Free Software Foundation, Inc., 51
19 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 from optparse import OptionParser
22 from os import access, environ, mkdir, getpid, X_OK
33 from shutil import rmtree
34 from subprocess import call
35 from statistics import median
39 USAGE = """%prog [options] <compiler> [compiler options] <source code file>"""
42 This program compiles a C/C++ file with/without ccache a number of times to get
43 some idea of ccache speedup and overhead in the preprocessor and direct modes.
44 The arguments to the program should be the compiler, optionally followed by
45 compiler options, and finally the source file to compile. The compiler options
46 must not contain -c or -o as these options will be added later. Example:
47 misc/performance gcc -g -O2 -Idir file.c
50 DEFAULT_CCACHE = "./ccache"
51 DEFAULT_DIRECTORY = "."
52 DEFAULT_HIT_FACTOR = 1
57 "with ccache, preprocessor mode, cache miss",
58 "with ccache, preprocessor mode, cache hit",
59 "with ccache, direct mode, cache miss",
60 "with ccache, direct mode, cache hit",
61 "with ccache, depend mode, cache miss",
62 "with ccache, depend mode, cache hit",
80 def test(tmp_dir, options, compiler_args, source_file):
81 src_dir = "%s/src" % tmp_dir
82 obj_dir = "%s/obj" % tmp_dir
83 ccache_dir = "%s/ccache" % tmp_dir
87 compiler_args += ["-c", "-o"]
88 extension = splitext(source_file)[1]
89 hit_factor = options.hit_factor
92 progress("Creating source code\n")
93 for i in range(times):
94 with open("%s/%d%s" % (src_dir, i, extension), "w") as fp:
95 with open(source_file) as fp2:
98 fp.write("\nint ccache_perf_test_%d;\n" % i)
100 environment = {"CCACHE_DIR": ccache_dir, "PATH": environ["PATH"]}
101 environment["CCACHE_COMPILERCHECK"] = options.compilercheck
102 if options.compression_level:
103 environment["CCACHE_COMPRESSLEVEL"] = str(options.compression_level)
104 if options.file_clone:
105 environment["CCACHE_FILECLONE"] = "1"
107 environment["CCACHE_HARDLINK"] = "1"
108 if options.no_compression:
109 environment["CCACHE_NOCOMPRESS"] = "1"
111 environment["CCACHE_NOCPP2"] = "1"
113 environment["CCACHE_NOSTATS"] = "1"
118 times, *, use_direct, use_depend, use_ccache=True, print_progress=True
121 for i in range(times):
122 obj = "%s/%d.o" % (obj_dir, i)
123 src = "%s/%d%s" % (src_dir, i, extension)
125 args = [options.ccache]
128 args += compiler_args + [obj, src]
129 env = environment.copy()
131 env["CCACHE_NODIRECT"] = "1"
133 env["CCACHE_DEPEND"] = "1"
137 if call(args, env=env) != 0:
139 'Error running "%s"; please correct\n' % " ".join(args)
142 timings.append(time() - t0)
145 # Warm up the disk cache.
146 recreate_dir(ccache_dir)
147 recreate_dir(obj_dir)
148 run(1, use_direct=True, use_depend=False, print_progress=False)
150 ###########################################################################
152 recreate_dir(ccache_dir)
153 recreate_dir(obj_dir)
154 progress("Compiling %s\n" % PHASES[0])
156 run(times, use_direct=False, use_depend=False, use_ccache=False)
160 ###########################################################################
162 recreate_dir(ccache_dir)
163 recreate_dir(obj_dir)
164 progress("Compiling %s\n" % PHASES[1])
165 results.append(run(times, use_direct=False, use_depend=False))
168 recreate_dir(obj_dir)
169 progress("Compiling %s\n" % PHASES[2])
171 for j in range(hit_factor):
172 res += run(times, use_direct=False, use_depend=False)
176 ###########################################################################
178 recreate_dir(ccache_dir)
179 recreate_dir(obj_dir)
180 progress("Compiling %s\n" % PHASES[3])
181 results.append(run(times, use_direct=True, use_depend=False))
184 recreate_dir(obj_dir)
185 progress("Compiling %s\n" % PHASES[4])
187 for j in range(hit_factor):
188 res += run(times, use_direct=True, use_depend=False)
192 ###########################################################################
194 recreate_dir(ccache_dir)
195 recreate_dir(obj_dir)
196 progress("Compiling %s\n" % PHASES[5])
197 results.append(run(times, use_direct=True, use_depend=True))
200 recreate_dir(obj_dir)
201 progress("Compiling %s\n" % PHASES[6])
203 for j in range(hit_factor):
204 res += run(times, use_direct=True, use_depend=True)
208 for i, x in enumerate(results):
209 results[i] = median(x)
213 def print_result_as_text(results):
214 for i, x in enumerate(PHASES):
216 "%-43s %6.4f s (%8.4f %%) (%8.4f x)"
218 x.capitalize() + ":",
220 100 * (results[i] / results[0]),
221 results[0] / results[i],
226 def print_result_as_xml(results):
227 print('<?xml version="1.0" encoding="UTF-8"?>')
228 print("<ccache-perf>")
229 for i, x in enumerate(PHASES):
230 print("<measurement>")
231 print("<name>%s</name>" % x.capitalize())
232 print("<seconds>%.4f</seconds>" % results[i])
233 print("<percent>%.4f</percent>" % (100 * (results[i] / results[0])))
234 print("<times>%.4f</times>" % (results[0] / results[i]))
235 print("</measurement>")
236 print("</ccache-perf>")
240 return "on" if x else "off"
243 def find_in_path(cmd):
247 for path in environ["PATH"].split(":"):
248 p = joinpath(path, cmd)
249 if isfile(p) and access(p, X_OK):
255 op = OptionParser(usage=USAGE, description=DESCRIPTION)
256 op.disable_interspersed_args()
258 "--ccache", help="location of ccache (default: %s)" % DEFAULT_CCACHE
261 "--compilercheck", help="specify compilercheck (default: mtime)"
264 "--no-compression", help="disable compression", action="store_true"
266 op.add_option("--compression-level", help="set compression level", type=int)
271 "where to create the temporary directory with the cache and other"
272 " files (default: %s)" % DEFAULT_DIRECTORY
275 op.add_option("--file-clone", help="use file cloning", action="store_true")
276 op.add_option("--hardlink", help="use hard links", action="store_true")
280 "how many times more to compile the file for cache hits (default:"
281 " %d)" % DEFAULT_HIT_FACTOR
286 "--no-cpp2", help="compile preprocessed output", action="store_true"
289 "--no-stats", help="don't write statistics", action="store_true"
295 "number of times to compile the file (default: %d)" % DEFAULT_TIMES
300 "-v", "--verbose", help="print progress messages", action="store_true"
302 op.add_option("--xml", help="print results as XML", action="store_true")
304 ccache=DEFAULT_CCACHE,
305 compilercheck="mtime",
306 directory=DEFAULT_DIRECTORY,
307 hit_factor=DEFAULT_HIT_FACTOR,
310 options, args = op.parse_args(argv[1:])
312 op.error("Missing arguments; pass -h/--help for help")
315 verbose = options.verbose
317 options.ccache = abspath(options.ccache)
319 compiler = find_in_path(args[0])
321 op.error("Could not find %s in PATH" % args[0])
322 if "ccache" in basename(realpath(compiler)):
324 "%s seems to be a symlink to ccache; please specify the path to"
325 " the real compiler instead" % compiler
330 "Compilation command: %s -c -o %s.o"
331 % (" ".join(args), splitext(argv[-1])[0])
333 print("Compilercheck:", options.compilercheck)
334 print("Compression:", on_off(not options.no_compression))
335 print("Compression level:", options.compression_level or "default")
336 print("File cloning:", on_off(options.file_clone))
337 print("Hard linking:", on_off(options.hardlink))
338 print("No cpp2:", on_off(options.no_cpp2))
339 print("No stats:", on_off(options.no_stats))
341 tmp_dir = "%s/perfdir.%d" % (abspath(options.directory), getpid())
342 recreate_dir(tmp_dir)
343 results = test(tmp_dir, options, args[:-1], args[-1])
346 print_result_as_xml(results)
348 print_result_as_text(results)