]> git.ipfire.org Git - thirdparty/valgrind.git/commitdiff
First attempt at some performance tracking tools. Includes a script vg_perf
authorNicholas Nethercote <njn@valgrind.org>
Sat, 10 Dec 2005 23:11:28 +0000 (23:11 +0000)
committerNicholas Nethercote <njn@valgrind.org>
Sat, 10 Dec 2005 23:11:28 +0000 (23:11 +0000)
(use "make perf" to run) that executes test programs and times their
slowdowns under various tools.  It works a lot like the vg_regtest script.
It's a bit rough around the edges -- eg. you can't currently directly
compare two different versions of Valgrind, which would be useful -- but it
is a good start.

There are currently two test programs in perf/.  More will be added as time
goes on.  This stuff will be built on so that performance changes can be
tracked over time.

git-svn-id: svn://svn.valgrind.org/valgrind/trunk@5323

Makefile.am
configure.in
perf/Makefile.am [new file with mode: 0644]
perf/ffbench.c [new file with mode: 0644]
perf/ffbench.vgperf [new file with mode: 0644]
perf/sarp.c [new file with mode: 0644]
perf/sarp.vgperf [new file with mode: 0644]
perf/vg_perf.in [new file with mode: 0644]

index 9fb0418a93709c7a9c349c65333dd70bb93a77b8..9df0d8b9076b0b7e15db6ae327b4f636a82b7b3b 100644 (file)
@@ -16,7 +16,7 @@ TOOLS =               memcheck \
 # And we want to include Addrcheck in the distro, but not compile/test it.
 # Put docs last because building the HTML is slow and we want to get
 # everything else working before we try it.
-SUBDIRS = include coregrind . tests auxprogs $(TOOLS) helgrind docs
+SUBDIRS = include coregrind . tests perf auxprogs $(TOOLS) helgrind docs
 DIST_SUBDIRS  = $(SUBDIRS) addrcheck
 
 SUPP_FILES = \
@@ -58,6 +58,10 @@ default.supp: $(SUPP_FILES)
 regtest: check
        @PERL@ tests/vg_regtest $(TOOLS)
 
+## Preprend @PERL@ because tests/vg_per isn't executable
+perf: check
+       @PERL@ perf/vg_perf perf
+
 EXTRA_DIST = \
        ACKNOWLEDGEMENTS \
        README_DEVELOPERS \
index 5681fd79d783554af4add104654f1a33c6a59e8a..dceba7b3c64cf7653ccc442bdd5f2dd3b1d743cc 100644 (file)
@@ -496,6 +496,8 @@ AC_OUTPUT(
    docs/xml/Makefile
    tests/Makefile 
    tests/vg_regtest 
+   perf/Makefile 
+   perf/vg_perf
    include/Makefile 
    auxprogs/Makefile
    coregrind/Makefile 
diff --git a/perf/Makefile.am b/perf/Makefile.am
new file mode 100644 (file)
index 0000000..d703a67
--- /dev/null
@@ -0,0 +1,17 @@
+
+noinst_SCRIPTS = vg_perf
+
+EXTRA_DIST = $(noinst_SCRIPTS) \
+       ffbench.vgperf \
+       sarp.vgperf
+
+check_PROGRAMS = \
+       ffbench sarp
+
+AM_CFLAGS   = $(WERROR) -Winline -Wall -Wshadow -g -O
+AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/include -I$(top_builddir)/include
+AM_CXXFLAGS = $(AM_CFLAGS)
+
+# Extra stuff
+ffbench_LDADD          = -lm
+
diff --git a/perf/ffbench.c b/perf/ffbench.c
new file mode 100644 (file)
index 0000000..e7e3523
--- /dev/null
@@ -0,0 +1,382 @@
+// This small program computes a Fast Fourier Transform.  It tests
+// Valgrind's handling of FP operations.  It is representative of all
+// programs that do a lot of FP operations.
+
+// This program was taken from http://www.fourmilab.ch/.  The front page of
+// that site says:
+//
+//   "Except for a few clearly-marked exceptions, all the material on this
+//   site is in the public domain and may be used in any manner without
+//   permission, restriction, attribution, or compensation."
+
+/*
+
+       Two-dimensional FFT benchmark
+
+       Designed and implemented by John Walker in April of 1989.
+
+       This  benchmark executes a specified number of passes (default
+       20) through a loop in which each  iteration  performs  a  fast
+       Fourier transform of a square matrix (default size 256x256) of
+       complex numbers (default precision double),  followed  by  the
+       inverse  transform.   After  all loop iterations are performed
+       the results are checked against known correct values.
+
+       This benchmark is intended for use on C implementations  which
+        define  "int"  as  32 bits or longer and permit allocation and
+       direct addressing of arrays larger than one megabyte.
+
+       If CAPOUT is defined,  the  result  after  all  iterations  is
+       written  as  a  CA  Lab  pattern  file.   This is intended for
+       debugging in case horribly wrong results  are  obtained  on  a
+       given machine.
+
+       Archival  timings  are  run  with the definitions below set as
+       follows: Float = double, Asize = 256, Passes = 20, CAPOUT  not
+       defined.
+
+       Time (seconds)              System
+
+            2393.93       Sun 3/260, SunOS 3.4, C, "-f68881 -O".
+                         (John Walker).
+
+            1928          Macintosh IIx, MPW C 3.0, "-mc68020
+                          -mc68881 -elems881 -m".  (Hugh Hoover).
+
+            1636.1        Sun 4/110, "cc -O3 -lm".  (Michael McClary).
+                         The suspicion is that this is software
+                         floating point.
+
+            1556.7        Macintosh II, A/UX, "cc -O -lm"
+                         (Michael McClary).
+
+           1388.8        Sun 386i/250, SunOS 4.0.1 C
+                          "-O /usr/lib/trig.il".  (James Carrington).
+
+           1331.93       Sun 3/60, SunOS 4.0.1, C,
+                          "-O4 -f68881 /usr/lib/libm.il"
+                         (Bob Elman).
+
+            1204.0        Apollo Domain DN4000, C, "-cpu 3000 -opt 4".
+                         (Sam Crupi).
+
+           1174.66       Compaq 386/25, SCO Xenix 386 C.
+                         (Peter Shieh).
+
+           1068          Compaq 386/25, SCO Xenix 386,
+                         Metaware High C.  (Robert Wenig).
+
+           1064.0        Sun 3/80, SunOS 4.0.3 Beta C
+                          "-O3 -f68881 /usr/lib/libm.il".  (James Carrington).
+
+           1061.4        Compaq 386/25, SCO Xenix, High C 1.4.
+                         (James Carrington).
+
+           1059.79       Compaq 386/25, 387/25, High C 1.4,
+                         DOS|Extender 2.2, 387 inline code
+                         generation.  (Nathan Bender).
+
+            777.14       Compaq 386/25, IIT 3C87-25 (387 Compatible),
+                         High C 1.5, DOS|Extender 2.2, 387 inline
+                         code generation.  (Nathan Bender).
+
+            751          Compaq DeskPro 386/33, High C 1.5 + DOS|Extender,
+                         387 code generation.  (James Carrington).
+
+            431.44       Compaq 386/25, Weitek 3167-25, DOS 3.31,
+                         High C 1.4, DOS|Extender, Weitek code generation.
+                         (Nathan Bender).
+
+            344.9        Compaq 486/25, Metaware High C 1.6, Phar Lap
+                         DOS|Extender, in-line floating point.  (Nathan
+                         Bender).
+
+            324.2        Data General Motorola 88000, 16 Mhz, Gnu C.
+
+             323.1        Sun 4/280, C, "-O4".  (Eric Hill).
+
+            254          Compaq SystemPro 486/33, High C 1.5 + DOS|Extender,
+                         387 code generation.  (James Carrington).
+
+            242.8        Silicon Graphics Personal IRIS, MIPS R2000A,
+                          12.5 Mhz, "-O3" (highest level optimisation).
+                         (Mike Zentner).
+
+             233.0        Sun SPARCStation 1, C, "-O4", SunOS 4.0.3.
+                         (Nathan Bender).
+
+            187.30       DEC PMAX 3100, MIPS 2000 chip.
+                         (Robert Wenig).
+
+             120.46       Sun SparcStation 2, C, "-O4", SunOS 4.1.1.
+                         (John Walker).
+
+             120.21       DEC 3MAX, MIPS 3000, "-O4".
+
+             98.0        Intel i860 experimental environment,
+                         OS/2, data caching disabled.  (Kern
+                         Sibbald).
+
+             34.9        Silicon Graphics Indigo², MIPS R4400,
+                          175 Mhz, IRIX 5.2, "-O".
+
+             32.4        Pentium 133, Windows NT, Microsoft Visual
+                         C++ 4.0.
+
+             17.25       Silicon Graphics Indigo², MIPS R4400,
+                          175 Mhz, IRIX 6.5, "-O3".
+
+             14.10       Dell Dimension XPS R100, Pentium II 400 MHz,
+                         Windows 98, Microsoft Visual C 5.0.
+
+             10.7        Hewlett-Packard Kayak XU 450Mhz Pentium II,
+                         Microsoft Visual C++ 6.0, Windows NT 4.0sp3.  (Nathan Bender).
+
+              5.09       Sun Ultra 2, UltraSPARC V9, 300 MHz, gcc -O3.
+              
+              0.846      Dell Inspiron 9100, Pentium 4, 3.4 GHz, gcc -O3.
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+
+/*  The  program  may  be  run with  Float defined as either float or
+    double.  With IEEE arithmetic, the same answers are generated  for
+    either floating point mode.  */
+
+#define Float   double            /* Floating point type used in FFT */
+
+#define Asize   256               /* Array edge size */
+#define Passes  20                /* Number of FFT/Inverse passes */
+
+#define max(a,b) ((a)>(b)?(a):(b))
+#define min(a,b) ((a)<=(b)?(a):(b))
+
+#ifndef unix
+#ifndef WIN32
+extern char *farmalloc(long s);
+#define malloc(x)   farmalloc(x)
+#endif
+#define FWMODE   "wb"
+#else
+#define FWMODE   "w"
+#endif
+
+/*
+
+       Multi-dimensional fast Fourier transform
+
+        Adapted from Press et al., "Numerical Recipes in C".
+
+*/
+
+#define SWAP(a,b) tempr=(a); (a)=(b); (b)=tempr
+
+static void fourn(data, nn, ndim, isign)
+  Float data[];
+  int nn[], ndim, isign;
+{
+       register int i1, i2, i3;
+       int i2rev, i3rev, ip1, ip2, ip3, ifp1, ifp2;
+       int ibit, idim, k1, k2, n, nprev, nrem, ntot;
+       Float tempi, tempr;
+       double theta, wi, wpi, wpr, wr, wtemp;
+
+       ntot = 1;
+       for (idim = 1; idim <= ndim; idim++)
+          ntot *= nn[idim];
+       nprev = 1;
+       for (idim = ndim; idim >= 1; idim--) {
+          n = nn[idim];
+          nrem = ntot / (n * nprev);
+          ip1 = nprev << 1;
+          ip2 = ip1 * n;
+          ip3 = ip2 * nrem;
+          i2rev = 1;
+          for (i2 = 1; i2 <= ip2; i2 += ip1) {
+             if (i2 < i2rev) {
+                for (i1 = i2; i1 <= i2 + ip1 - 2; i1 += 2) {
+                   for (i3 = i1; i3 <= ip3; i3 += ip2) {
+                      i3rev = i2rev + i3 - i2;
+                      SWAP(data[i3], data[i3rev]);
+                      SWAP(data[i3 + 1], data[i3rev + 1]);
+                   }
+                }
+             }
+             ibit = ip2 >> 1;
+             while (ibit >= ip1 && i2rev > ibit) {
+                i2rev -= ibit;
+                ibit >>= 1;
+             }
+             i2rev += ibit;
+          }
+          ifp1 = ip1;
+          while (ifp1 < ip2) {
+             ifp2 = ifp1 << 1;
+             theta = isign * 6.28318530717959 / (ifp2 / ip1);
+             wtemp = sin(0.5 * theta);
+             wpr = -2.0 * wtemp * wtemp;
+             wpi = sin(theta);
+             wr = 1.0;
+             wi = 0.0;
+             for (i3 = 1; i3 <= ifp1; i3 += ip1) {
+                for (i1 = i3; i1 <= i3 + ip1 - 2; i1 += 2) {
+                   for (i2 = i1; i2 <= ip3; i2 += ifp2) {
+                      k1 = i2;
+                      k2 = k1 + ifp1;
+                      tempr = wr * data[k2] - wi * data[k2 + 1];
+                      tempi = wr * data[k2 + 1] + wi * data[k2];
+                      data[k2] = data[k1] - tempr;
+                      data[k2 + 1] = data[k1 + 1] - tempi;
+                      data[k1] += tempr;
+                      data[k1 + 1] += tempi;
+                   }
+                }
+                wr = (wtemp = wr) * wpr - wi * wpi + wr;
+                wi = wi * wpr + wtemp * wpi + wi;
+             }
+             ifp1 = ifp2;
+          }
+          nprev *= n;
+       }
+}
+#undef SWAP
+
+int main()
+{
+       int i, j, k, l, m, npasses = Passes, faedge;
+       Float *fdata /* , *fd */ ;
+       static int nsize[] = {0, 0, 0};
+       long fanum, fasize;
+       double mapbase, mapscale, /* x, */ rmin, rmax, imin, imax;
+
+       faedge = Asize;            /* FFT array edge size */
+       fanum = faedge * faedge;   /* Elements in FFT array */
+       fasize = ((fanum + 1) * 2 * sizeof(Float)); /* FFT array size */
+       nsize[1] = nsize[2] = faedge;
+
+       fdata = (Float *) malloc(fasize);
+       if (fdata == NULL) {
+           fprintf(stdout, "Can't allocate data array.\n");
+          exit(1);
+       }
+
+       /*  Generate data array to process.  */
+
+#define Re(x,y) fdata[1 + (faedge * (x) + (y)) * 2]
+#define Im(x,y) fdata[2 + (faedge * (x) + (y)) * 2]
+
+       memset(fdata, 0, fasize);
+       for (i = 0; i < faedge; i++) {
+          for (j = 0; j < faedge; j++) {
+             if (((i & 15) == 8) || ((j & 15) == 8))
+                Re(i, j) = 128.0;
+          }
+       }
+
+       for (i = 0; i < npasses; i++) {
+/*printf("Pass %d\n", i);*/
+          /* Transform image to frequency domain. */
+          fourn(fdata, nsize, 2, 1);
+
+          /*  Back-transform to image. */
+          fourn(fdata, nsize, 2, -1);
+       }
+
+       {
+          double r, ij, ar, ai;
+          rmin = 1e10; rmax = -1e10;
+          imin = 1e10; imax = -1e10;
+          ar = 0;
+          ai = 0;
+
+          for (i = 1; i <= fanum; i += 2) {
+             r = fdata[i];
+             ij = fdata[i + 1];
+             ar += r;
+             ai += ij;
+             rmin = min(r, rmin);
+             rmax = max(r, rmax);
+             imin = min(ij, imin);
+             imax = max(ij, imax);
+          }
+#ifdef DEBUG
+           printf("Real min %.4g, max %.4g.  Imaginary min %.4g, max %.4g.\n",
+             rmin, rmax, imin, imax);
+           printf("Average real %.4g, imaginary %.4g.\n", 
+             ar / fanum, ai / fanum);
+#endif
+          mapbase = rmin;
+          mapscale = 255 / (rmax - rmin);
+       }
+
+       /* See if we got the right answers. */
+
+       m = 0;
+       for (i = 0; i < faedge; i++) {
+          for (j = 0; j < faedge; j++) {
+             k = (Re(i, j) - mapbase) * mapscale;
+             l = (((i & 15) == 8) || ((j & 15) == 8)) ? 255 : 0;
+             if (k != l) {
+                m++;
+                fprintf(stdout,
+                    "Wrong answer at (%d,%d)!  Expected %d, got %d.\n",
+                   i, j, l, k);
+             }
+          }
+       }
+       if (m == 0) {
+           fprintf(stdout, "%d passes.  No errors in results.\n", npasses);
+       } else {
+           fprintf(stdout, "%d passes.  %d errors in results.\n",
+             npasses, m);
+       }
+
+#ifdef CAPOUT
+
+       /* Output the result of the transform as a CA Lab pattern
+          file for debugging. */
+
+       {
+#define SCRX  322
+#define SCRY  200
+#define SCRN  (SCRX * SCRY)
+          unsigned char patarr[SCRY][SCRX];
+          FILE *fp;
+
+/*  Map user external state numbers to internal state index  */
+
+#define UtoI(x)     (((((x) >> 1) & 0x7F) | ((x) << 7)) & 0xFF)
+
+          /* Copy data from FFT buffer to map. */
+
+          memset(patarr, 0, sizeof patarr);
+          l = (SCRX - faedge) / 2;
+          m = (faedge > SCRY) ? 0 : ((SCRY - faedge) / 2);
+          for (i = 1; i < faedge; i++) {
+             for (j = 0; j < min(SCRY, faedge); j++) {
+                k = (Re(i, j) - mapbase) * mapscale;
+                patarr[j + m][i + l] = UtoI(k);
+             }
+          }
+
+          /* Dump pattern map to file. */
+
+           fp = fopen("fft.cap", "w");
+          if (fp == NULL) {
+              fprintf(stdout, "Cannot open output file.\n");
+             exit(0);
+          }
+           putc(':', fp);
+          putc(1, fp);
+          fwrite(patarr, SCRN, 1, fp);
+          putc(6, fp);
+          fclose(fp);
+       }
+#endif
+
+       return 0;
+}
diff --git a/perf/ffbench.vgperf b/perf/ffbench.vgperf
new file mode 100644 (file)
index 0000000..98dae55
--- /dev/null
@@ -0,0 +1,2 @@
+prog: ffbench
+tools: none memcheck
diff --git a/perf/sarp.c b/perf/sarp.c
new file mode 100644 (file)
index 0000000..04518d6
--- /dev/null
@@ -0,0 +1,46 @@
+// This artificial program allocates and deallocates a lot of large objects
+// on the stack.  It is a stress test for Memcheck's set_address_range_perms
+// (sarp) function.  Pretty much all Valgrind versions up to 3.1.X do very
+// badly on it, ie. a slowdown of at least 100x.
+//
+// It is representative of tsim_arch, the simulator for the University of
+// Texas's TRIPS processor, whose performance under Valgrind is dominated by
+// the handling of one frequently-called function that allocates 8348 bytes
+// on the stack.
+
+#include <assert.h>
+#include <time.h>
+
+#define REPS   1000*1000
+
+int f(int i)
+{
+   // This nonsense is just to ensure that the compiler does not optimise
+   // away the stack allocation.
+   char big_array[8348];
+   big_array[0]    = 12;
+   big_array[2333] = 34;
+   big_array[5678] = 56;
+   big_array[8347] = 78;
+   assert( 8000 == (&big_array[8100] - &big_array[100]) );
+   return big_array[i];
+}
+
+int main(void)
+{
+   int i, sum = 0;
+
+   struct timespec req;
+   req.tv_sec  = 0;
+   req.tv_nsec = 100*1000*1000;   // 0.1s
+
+   // Pause for a bit so that the native run-time is not 0.00, which leads
+   // to ridiculous slow-down figures.
+   nanosleep(&req, NULL);
+   
+   for (i = 0; i < REPS; i++) {
+      sum += f(i & 0xff);
+   }
+   return sum % 256;
+}
+
diff --git a/perf/sarp.vgperf b/perf/sarp.vgperf
new file mode 100644 (file)
index 0000000..83c634a
--- /dev/null
@@ -0,0 +1,2 @@
+prog: sarp
+tools: none memcheck
diff --git a/perf/vg_perf.in b/perf/vg_perf.in
new file mode 100644 (file)
index 0000000..9199152
--- /dev/null
@@ -0,0 +1,368 @@
+#! @PERL@
+##--------------------------------------------------------------------##
+##--- Valgrind performance testing script                  vg_perf ---##
+##--------------------------------------------------------------------##
+
+#  This file is part of Valgrind, a dynamic binary instrumentation
+#  framework.
+#
+#  Copyright (C) 2005 Nicholas Nethercote
+#     njn@valgrind.org
+#
+#  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 2 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., 59 Temple Place, Suite 330, Boston, MA
+#  02111-1307, USA.
+#
+#  The GNU General Public License is contained in the file COPYING.
+
+#----------------------------------------------------------------------------
+# usage: vg_perf [options] <dirs | files>
+#
+# Options:
+#   --all:      run tests in all subdirs
+#   --valgrind: valgrind to use (the directory it's in).  Default is the one
+#               in the current tree.
+#
+# The easiest way is to run all tests in valgrind/ with (assuming you installed
+# in $PREFIX):
+#
+#   perl perf/vg_perf --all
+#
+# You can specify individual files to test, or whole directories, or both.
+# Directories are traversed recursively, except for ones named, for example, 
+# CVS/ or docs/.
+#
+# Each test is defined in a file <test>.vgperf, containing one or more of the
+# following lines, in any order:
+#   - prog:   <prog to run>                         (compulsory)
+#   - tools:  <Valgrind tools>                      (compulsory)
+#   - args:   <args for prog>                       (default: none)
+#   - vgopts: <Valgrind options>                    (default: none)
+#   - prereq: <prerequisite command>                (default: none)
+#   - cleanup: <post-test cleanup cmd to run>       (default: none)
+#
+# The prerequisite command, if present, must return 0 otherwise the test is
+# skipped.
+#----------------------------------------------------------------------------
+
+use warnings;
+use strict;
+
+#----------------------------------------------------------------------------
+# Global vars
+#----------------------------------------------------------------------------
+my $usage="vg_perf [--all, --valgrind]\n";
+
+my $tmp="vg_perf.tmp.$$";
+
+# Test variables
+my $vgopts;             # valgrind options
+my $prog;               # test prog
+my $args;               # test prog args
+my $prereq;             # prerequisite test to satisfy before running test
+my $cleanup;            # cleanup command to run
+my @tools;              # which tools are we measuring the program with
+
+# Abbreviations used in output
+my %toolnames = ( 
+    none        => "nl",
+    memcheck    => "mc",
+    cachegrind  => "cg",
+    massif      => "ms"
+);
+
+# We run each program this many times and choose the best time.
+my $n_runs = 3;
+
+my $num_tests_done   = 0;
+my $num_timings_done = 0;
+
+# Starting directory
+chomp(my $tests_dir = `pwd`);
+
+# Directory of the Valgrind being measured.  Default is the one in the
+# current tree.
+my $vg_dir = $tests_dir;
+
+#----------------------------------------------------------------------------
+# Process command line, setup
+#----------------------------------------------------------------------------
+
+# If $prog is a relative path, it prepends $dir to it.  Useful for two reasons:
+#
+# 1. Can prepend "." onto programs to avoid trouble with users who don't have
+#    "." in their path (by making $dir = ".")
+# 2. Can prepend the current dir to make the command absolute to avoid
+#    subsequent trouble when we change directories.
+#
+# Also checks the program exists and is executable.
+sub validate_program ($$$$) 
+{
+    my ($dir, $prog, $must_exist, $must_be_executable) = @_;
+
+    # If absolute path, leave it alone.  If relative, make it
+    # absolute -- by prepending current dir -- so we can change
+    # dirs and still use it.
+    $prog = "$dir/$prog" if ($prog !~ /^\//);
+    if ($must_exist) {
+        (-f $prog) or die "vg_perf: '$prog' not found or not a file ($dir)\n";
+    }
+    if ($must_be_executable) { 
+        (-x $prog) or die "vg_perf: '$prog' not executable ($dir)\n";
+    }
+
+    return $prog;
+}
+
+sub validate_tools($)
+{
+    # XXX: should check they exist!
+    my ($toolnames) = @_;
+    my @t = split(/\s+/, $toolnames);
+    return @t;
+}
+
+sub process_command_line() 
+{
+    my $alldirs = 0;
+    my @fs;
+    
+    for my $arg (@ARGV) {
+        if ($arg =~ /^-/) {
+            if      ($arg =~ /^--all$/) {
+                $alldirs = 1;
+            } elsif ($arg =~ /^--valgrind=(.*)$/) {
+                $vg_dir = $1;
+            } else {
+                die $usage;
+            }
+        } else {
+            push(@fs, $arg);
+        }
+    }
+    # Make $vg_dir absolute if not already
+    if ($vg_dir !~ /^\//) { $vg_dir = "$tests_dir/$vg_dir"; }
+    validate_program($vg_dir, "./coregrind/valgrind", 1, 1);
+
+    if ($alldirs) {
+        @fs = ();
+        foreach my $f (glob "*") {
+            push(@fs, $f) if (-d $f);
+        }
+    }
+
+    (0 != @fs) or die "No test files or directories specified\n";
+
+    return @fs;
+}
+
+#----------------------------------------------------------------------------
+# Read a .vgperf file
+#----------------------------------------------------------------------------
+sub read_vgperf_file($)
+{
+    my ($f) = @_;
+
+    # Defaults.
+    ($vgopts, $prog, $args, $prereq, $cleanup)
+      = ("", undef, "", undef, undef, undef, undef);
+
+    open(INPUTFILE, "< $f") || die "File $f not openable\n";
+
+    while (my $line = <INPUTFILE>) {
+        if      ($line =~ /^\s*#/ || $line =~ /^\s*$/) {
+           next;
+       } elsif ($line =~ /^\s*vgopts:\s*(.*)$/) {
+            $vgopts = $1;
+        } elsif ($line =~ /^\s*prog:\s*(.*)$/) {
+            $prog = validate_program(".", $1, 0, 0);
+        } elsif ($line =~ /^\s*tools:\s*(.*)$/) {
+            @tools = validate_tools($1);
+        } elsif ($line =~ /^\s*args:\s*(.*)$/) {
+            $args = $1;
+        } elsif ($line =~ /^\s*prereq:\s*(.*)$/) {
+            $prereq = $1;
+        } elsif ($line =~ /^\s*cleanup:\s*(.*)$/) {
+            $cleanup = $1;
+        } else {
+            die "Bad line in $f: $line\n";
+        }
+    }
+    close(INPUTFILE);
+
+    if (!defined $prog) {
+        $prog = "";     # allow no prog for testing error and --help cases
+    }
+    if (0 == @tools) {
+        die "vg_perf: missing 'tools' line in $f\n";
+    }
+}
+
+#----------------------------------------------------------------------------
+# Do one test
+#----------------------------------------------------------------------------
+# Since most of the program time is spent in system() calls, need this to
+# propagate a Ctrl-C enabling us to quit.
+sub mysystem($) 
+{
+    (system($_[0]) != 2) or exit 1;      # 2 is SIGINT
+}
+
+# Run program N times, return the best wall-clock time.
+sub time_prog($$)
+{
+    my ($cmd, $n) = @_;
+    my $tmin = 999999;
+    for (my $i = 0; $i < $n; $i++) {
+        my $out = `$cmd 2>&1 1>/dev/null`;
+        $out =~ /walltime: ([\d\.]+)s/;
+        $tmin = $1 if ($1 < $tmin);
+    }
+    return $tmin;
+}
+
+sub do_one_test($$) 
+{
+    my ($dir, $vgperf) = @_;
+    $vgperf =~ /^(.*)\.vgperf/;
+    my $name = $1;
+
+    read_vgperf_file($vgperf);
+
+    if (defined $prereq) {
+        if (system("$prereq") != 0) {
+            printf("%-16s (skipping, prereq failed: $prereq)\n", "$name:");
+            return;
+        }
+    }
+
+    printf("%-12s", "$name:");
+
+    my $timecmd = "/usr/bin/time -f 'walltime: %es'";
+
+    # Do the native run(s).
+    printf("nt:");
+    my $cmd     = "$timecmd $prog $args";
+    my $tNative = time_prog($cmd, $n_runs);
+    printf("%4.1fs  ", $tNative);
+
+    foreach my $tool (@tools) {
+        (defined $toolnames{$tool}) or 
+            die "unknown tool $tool, please add to %toolnames\n";
+
+        # Do the tool run(s).  Set both VALGRIND_LIB and VALGRIND_LIB_INNER
+        # in case this Valgrind was configured with --enable-inner.
+        printf("%s:", $toolnames{$tool});
+        my $vgsetup = "VALGRIND_LIB=$vg_dir/.in_place "
+                    . "VALGRIND_LIB_INNER=$vg_dir/.in_place ";
+        my $vgcmd   = "$vg_dir/coregrind/valgrind "
+                    . "--command-line-only=yes --tool=$tool -q "
+                    . "--memcheck:leak-check=no --addrcheck:leak-check=no "
+                    . "$vgopts ";
+        my $cmd     = "$vgsetup $timecmd $vgcmd $prog $args";
+        my $tTool   = time_prog($cmd, $n_runs);
+        printf("%4.1fs (%4.1fx)  ", $tTool, $tTool/$tNative);
+
+        $num_timings_done++;
+    }
+    printf("\n");
+
+    if (defined $cleanup) {
+        (system("$cleanup") == 0) or 
+            print("  ($name cleanup operation failed: $cleanup)\n");
+    }
+
+    $num_tests_done++;
+}
+
+#----------------------------------------------------------------------------
+# Test one directory (and any subdirs)
+#----------------------------------------------------------------------------
+sub test_one_dir($$);    # forward declaration
+
+sub test_one_dir($$) 
+{
+    my ($dir, $prev_dirs) = @_;
+    $dir =~ s/\/$//;    # trim a trailing '/'
+
+    # Ignore dirs into which we should not recurse.
+    if ($dir =~ /^(BitKeeper|CVS|SCCS|docs|doc)$/) { return; }
+
+    chdir($dir) or die "Could not change into $dir\n";
+
+    # Nb: Don't prepend a '/' to the base directory
+    my $full_dir = $prev_dirs . ($prev_dirs eq "" ? "" : "/") . $dir;
+    my $dashes = "-" x (50 - length $full_dir);
+
+    my @fs = glob "*";
+    my $found_tests = (0 != (grep { $_ =~ /\.vgperf$/ } @fs));
+
+    if ($found_tests) {
+        print "-- Running  tests in $full_dir $dashes\n";
+    }
+    foreach my $f (@fs) {
+        if (-d $f) {
+            test_one_dir($f, $full_dir);
+        } elsif ($f =~ /\.vgperf$/) {
+            do_one_test($full_dir, $f);
+        }
+    }
+    if ($found_tests) {
+        print "-- Finished tests in $full_dir $dashes\n";
+    }
+
+    chdir("..");
+}
+
+#----------------------------------------------------------------------------
+# Summarise results
+#----------------------------------------------------------------------------
+sub summarise_results 
+{
+    printf("\n== %d programs, %d timings =================\n\n", 
+           $num_tests_done, $num_timings_done);
+}
+
+#----------------------------------------------------------------------------
+# main()
+#----------------------------------------------------------------------------
+
+# nuke VALGRIND_OPTS
+$ENV{"VALGRIND_OPTS"} = "";
+
+my @fs = process_command_line();
+foreach my $f (@fs) {
+    if (-d $f) {
+        test_one_dir($f, "");
+    } else { 
+        # Allow the .vgperf suffix to be given or omitted
+        if ($f =~ /.vgperf$/ && -r $f) {
+            # do nothing
+        } elsif (-r "$f.vgperf") {
+            $f = "$f.vgperf";
+        } else {
+            die "`$f' neither a directory nor a readable test file/name\n"
+        }
+        my $dir  = `dirname  $f`;   chomp $dir;
+        my $file = `basename $f`;   chomp $file;
+        chdir($dir) or die "Could not change into $dir\n";
+        do_one_test($dir, $file);
+        chdir($tests_dir);
+    }
+}
+summarise_results();
+
+##--------------------------------------------------------------------##
+##--- end                                                          ---##
+##--------------------------------------------------------------------##