From: Joel Rosdahl Date: Sun, 2 May 2010 13:04:44 +0000 (+0200) Subject: Improve performance measurement script X-Git-Tag: v3.0pre1~31 X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=2dc0d9323811f51d8182bccbc1c0ab854b2a0186;p=thirdparty%2Fccache.git Improve performance measurement script --- diff --git a/.gitignore b/.gitignore index 380ca28c8..a9601264d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,5 @@ config.log config.status configure dev.mk +perfdir.* testdir.* -tmpdir.* diff --git a/Makefile.in b/Makefile.in index 9e6dc8d06..eed2f581b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -56,11 +56,11 @@ clean: rm -f $(files_to_clean) .PHONY: perf -perf: - CXX='$(CXX)' $(srcdir)/perf.sh +perf: ccache$(EXEEXT) + $(srcdir)/perf.py --ccache ccache$(EXEEXT) $(CC) $(CFLAGS) $(CPPFLAGS) $(srcdir)/ccache.c .PHONY: test -test: +test: ccache$(EXEEXT) CC='$(CC)' $(srcdir)/test.sh .PHONY: check diff --git a/perf.py b/perf.py new file mode 100755 index 000000000..4357a5aea --- /dev/null +++ b/perf.py @@ -0,0 +1,248 @@ +#! /usr/bin/env python +# +# Copyright (C) 2010 Joel Rosdahl +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +from optparse import OptionParser +from os import access, environ, mkdir, getpid, X_OK +from os.path import ( + abspath, basename, exists, isabs, isfile, join as joinpath, realpath, + splitext) +from shutil import rmtree +from subprocess import call +from time import time +import sys + +USAGE = """%prog [options] [compiler options] """ + +DESCRIPTION = """\ +This program compiles a C/C++ file with/without ccache a number of times to get +some idea of ccache speedup and overhead in the preprocessor and direct modes. +The arguments to the program should be the compiler, optionally followed by +compiler options, and finally the source file to compile. The compiler options +must not contain -c or -o as these options will be added later. Example: +./perf.py gcc -g -O2 -Idir file.c +""" + +DEFAULT_CCACHE = "./ccache" +DEFAULT_DIRECTORY = "." +DEFAULT_TIMES = 30 + +PHASES = [ + "without ccache", + "with ccache, preprocessor mode, cache miss", + "with ccache, preprocessor mode, cache hit", + "with ccache, direct mode, cache miss", + "with ccache, direct mode, cache hit"] + +verbose = False + +def progress(msg): + if verbose: + sys.stdout.write(msg) + sys.stdout.flush() + +def recreate_dir(x): + if exists(x): + rmtree(x) + mkdir(x) + +def test(tmp_dir, options, compiler_args, source_file): + src_dir = "%s/src" % tmp_dir + obj_dir = "%s/obj" % tmp_dir + ccache_dir = "%s/ccache" % tmp_dir + mkdir(src_dir) + mkdir(obj_dir) + + compiler_args += ["-c", "-o"] + extension = splitext(source_file)[1] + times = options.times + + progress("Creating source code\n") + for i in range(times): + fp = open("%s/%d%s" % (src_dir, i, extension), "w") + fp.write(open(source_file).read()) + fp.write("\nint ccache_perf_test_%d;\n" % i) + fp.close() + + environment = {"CCACHE_DIR": ccache_dir, "PATH": environ["PATH"]} + if options.compression: + environment["CCACHE_COMPRESS"] = "1" + if options.hardlink: + environment["CCACHE_HARDLINK"] = "1" + + result = [None] * len(PHASES) + + def run(i, use_ccache, use_direct): + obj = "%s/%d.o" % (obj_dir, i) + src = "%s/%d%s" % (src_dir, i, extension) + if use_ccache: + args = [options.ccache] + else: + args = [] + args += compiler_args + [obj, src] + env = environment.copy() + if not use_direct: + env["CCACHE_NODIRECT"] = "1" + if call(args, env=env) != 0: + sys.stderr.write( + 'Error running "%s"; please correct\n' % " ".join(args)) + sys.exit(1) + + # Warm up the disk cache. + recreate_dir(ccache_dir) + recreate_dir(obj_dir) + run(0, True, True) + + ########################################################################### + # Without ccache + recreate_dir(ccache_dir) + recreate_dir(obj_dir) + progress("Compiling %s\n" % PHASES[0]) + t0 = time() + for i in range(times): + run(i, False, False) + progress(".") + result[0] = time() - t0 + progress("\n") + + ########################################################################### + # Preprocessor mode + recreate_dir(ccache_dir) + recreate_dir(obj_dir) + progress("Compiling %s\n" % PHASES[1]) + t0 = time() + for i in range(times): + run(i, True, False) + progress(".") + result[1] = time() - t0 + progress("\n") + + recreate_dir(obj_dir) + progress("Compiling %s\n" % PHASES[2]) + t0 = time() + for i in range(times): + run(i, True, False) + progress(".") + result[2] = time() - t0 + progress("\n") + + ########################################################################### + # Direct mode + recreate_dir(ccache_dir) + recreate_dir(obj_dir) + progress("Compiling %s\n" % PHASES[3]) + t0 = time() + for i in range(times): + run(i, True, True) + progress(".") + result[3] = time() - t0 + progress("\n") + + recreate_dir(obj_dir) + progress("Compiling %s\n" % PHASES[4]) + t0 = time() + for i in range(times): + run(i, True, True) + progress(".") + result[4] = time() - t0 + progress("\n") + + return result + +def print_result(result): + for (i, x) in enumerate(PHASES): + print "%-43s %6.2f s (%6.2f %%) (%5.2f x)" % ( + x.capitalize() + ":", + result[i], + 100 * (result[i] / result[0]), + result[0] / result[i]) + +def on_off(x): + return "on" if x else "off" + +def find_in_path(cmd): + if isabs(cmd): + return cmd + else: + for path in environ["PATH"].split(":"): + p = joinpath(path, cmd) + if isfile(p) and access(p, X_OK): + return p + return None + +def main(argv): + op = OptionParser(usage=USAGE, description=DESCRIPTION) + op.disable_interspersed_args() + op.add_option( + "--ccache", + help="location of ccache (default: %s)" % DEFAULT_CCACHE) + op.add_option( + "--compression", + help="use compression", + action="store_true") + op.add_option( + "-d", "--directory", + help="where to create the temporary directory with the cache and" \ + " other files (default: %s)" \ + % DEFAULT_DIRECTORY) + op.add_option( + "--hardlink", + help="use hard links", + action="store_true") + op.add_option( + "-n", "--times", + help="number of times to compile the file (default: %d)" \ + % DEFAULT_TIMES, + type="int") + op.add_option( + "-v", "--verbose", + help="print progress messages", + action="store_true") + op.set_defaults( + ccache=DEFAULT_CCACHE, + directory=DEFAULT_DIRECTORY, + times=DEFAULT_TIMES) + (options, args) = op.parse_args(argv[1:]) + if len(args) < 2: + op.error("Missing arguments; pass -h/--help for help") + + global verbose + verbose = options.verbose + + options.ccache = abspath(options.ccache) + + compiler = find_in_path(args[0]) + if compiler is None: + op.error("Could not find %s in PATH" % args[0]) + if "ccache" in basename(realpath(compiler)): + op.error( + "%s seems to be a symlink to ccache; please specify the path to" + " the real compiler instead" % compiler) + + print "Compilation command: %s -c -o %s.o" % ( + " ".join(args), + splitext(argv[-1])[0]) + print "Compression:", on_off(options.compression) + print "Hardlink:", on_off(options.hardlink) + + tmp_dir = "%s/perfdir.%d" % (abspath(options.directory), getpid()) + recreate_dir(tmp_dir) + result = test(tmp_dir, options, args[:-1], args[-1]) + rmtree(tmp_dir) + print_result(result) + +main(sys.argv) diff --git a/perf.sh b/perf.sh deleted file mode 100755 index d83cd0d75..000000000 --- a/perf.sh +++ /dev/null @@ -1,120 +0,0 @@ -#! /bin/sh - -set -e - -create_src() { - n=$1 - i=0 - while [ $i -lt $n ]; do - file=$i.cc - cat <$file -#include -#include -#include -#include -#include -#include -#include -#include -#include -int var$i; -EOF - i=$(($i + 1)) - done -} - -compile() { - local n=$1 - local compiler="$2" - local i=0 - local objdir=objs - rm -rf $objdir - mkdir -p $objdir - while [ $i -lt $n ]; do - echo -n . - $compiler -c $i.cc -O2 -o $objdir/$i.o - i=$(($i + 1)) - done - echo -} - -now() { - perl -e 'use Time::HiRes qw(time); print time' -} - -elapsed() { - perl -e 'use Time::HiRes qw(time); printf("%.3f\n", time - $ARGV[0])' $1 -} - -stat() { - local desc="$1" - local time=$2 - local ref_time=$3 - local perc=$(perl -e "print 100 * $time / $ref_time") - local factor=$(perl -e "print $ref_time / $time") - printf "%-43s %5.2f s (%6.2f %%) (%5.2f x)\n" "$desc:" $time $perc $factor -} - -############################################################################### - -if [ -n "$CXX" ]; then - cxx="$CXX" -else - cxx=/usr/bin/c++ -fi - -ccache=../ccache -tmpdir=tmpdir.$$ -CCACHE_DIR=.ccache -export CCACHE_DIR -CCACHE_NODIRECT=1 -export CCACHE_NODIRECT - -rm -rf $tmpdir -mkdir $tmpdir -cd $tmpdir - -if [ "$#" -gt 0 ]; then - n=$1 -else - n=30 -fi -create_src $n - -# Perform a compilation to warm up the disk cache. -compile 1 $cxx >/dev/null - -echo "Without ccache:" -t0=$(now) -compile $n $cxx -t_wo=$(elapsed $t0) - -echo "With ccache, no direct, cache miss:" -t0=$(now) -compile $n "$ccache $cxx" -t_p_m=$(elapsed $t0) - -echo "With ccache, no direct, cache hit:" -t0=$(now) -compile $n "$ccache $cxx" -t_p_h=$(elapsed $t0) - -unset CCACHE_NODIRECT -rm -rf $CCACHE_DIR - -echo "With ccache, direct, cache miss:" -t0=$(now) -compile $n "$ccache $cxx" -t_d_m=$(elapsed $t0) - -echo "With ccache, direct, cache hit:" -t0=$(now) -compile $n "$ccache $cxx" -t_d_h=$(elapsed $t0) - -echo -stat "Without ccache" $t_wo $t_wo -stat "With ccache, preprocessor mode, cache miss" $t_p_m $t_wo -stat "With ccache, preprocessor mode, cache hit" $t_p_h $t_wo -stat "With ccache, direct mode, cache miss" $t_d_m $t_wo -stat "With ccache, direct mode, cache hit" $t_d_h $t_wo