]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Improve performance measurement script
authorJoel Rosdahl <joel@rosdahl.net>
Sun, 2 May 2010 13:04:44 +0000 (15:04 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Sun, 2 May 2010 13:04:44 +0000 (15:04 +0200)
.gitignore
Makefile.in
perf.py [new file with mode: 0755]
perf.sh [deleted file]

index 380ca28c868ba81fe3ef604311e8132d6fa3fdbe..a9601264d9ae78574a2bd7b45469b986853deca4 100644 (file)
@@ -16,5 +16,5 @@ config.log
 config.status
 configure
 dev.mk
+perfdir.*
 testdir.*
-tmpdir.*
index 9e6dc8d06d7a7de1cb23bff3f5f9b38e60781c92..eed2f581b8269a3a578a4adc0b4300f9229ee81c 100644 (file)
@@ -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 (executable)
index 0000000..4357a5a
--- /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> [compiler options] <source code file>"""
+
+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 (executable)
index d83cd0d..0000000
--- 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 <<EOF >$file
-#include <algorithm>
-#include <deque>
-#include <iostream>
-#include <list>
-#include <map>
-#include <set>
-#include <sstream>
-#include <string>
-#include <vector>
-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