Added cg_diff.
git-svn-id: svn://svn.valgrind.org/valgrind/trunk@11193 a5019735-40e9-0310-863c-91ae7b9d1cf9
diff --git a/NEWS b/NEWS
index 8726e21..c41dc4e 100644
--- a/NEWS
+++ b/NEWS
@@ -3,9 +3,18 @@
~~~~~~~~~~~~~~~~~~~
Improvements:
- XXX: ARM support
+- XXX: Mac OS 10.6 support (32 and 64 bit)
+- XXX: Much faster startup on Mac OS 10.5 for 64-bit programs.
- --smc-check=all is much faster
+- Cachegrind has a new processing script, cg_diff, which finds the
+ difference between two profiles. It's very useful for evaluating the
+ performance effects of a change in a program.
+
+ Related to this change, the meaning of cg_annotate's (rarely-used)
+ --threshold option has changed; this is unlikely to affect many people, if
+ you do use it please see the user manual for details.
Release 3.5.0 (19 August 2009)
diff --git a/cachegrind/Makefile.am b/cachegrind/Makefile.am
index 81f37df..308e7d4 100644
--- a/cachegrind/Makefile.am
+++ b/cachegrind/Makefile.am
@@ -8,7 +8,7 @@
# Headers, etc
#----------------------------------------------------------------------------
-bin_SCRIPTS = cg_annotate
+bin_SCRIPTS = cg_annotate cg_diff
noinst_HEADERS = \
cg_arch.h \
diff --git a/cachegrind/cg_annotate.in b/cachegrind/cg_annotate.in
index 3a51d4b..9dc9565 100644
--- a/cachegrind/cg_annotate.in
+++ b/cachegrind/cg_annotate.in
@@ -120,7 +120,7 @@
# handled this proportion of all the events thresholded.
my @thresholds;
-my $default_threshold = 99;
+my $default_threshold = 0.1;
my $single_threshold = $default_threshold;
@@ -149,8 +149,8 @@
--version show version
--show=A,B,C only show figures for events A,B,C [all]
--sort=A,B,C sort columns by events A,B,C [event column order]
- --threshold=<0--100> percentage of counts (of primary sort event) we
- are interested in [$default_threshold%]
+ --threshold=<0--20> a function is shown if it accounts for more than x% of
+ the counts of the primary sort event [$default_threshold]
--auto=yes|no annotate all source files containing functions
that helped reach the event count threshold [no]
--context=N print N lines of context before and after
@@ -217,7 +217,7 @@
# --threshold=X (tolerates a trailing '%')
} elsif ($arg =~ /^--threshold=([\d\.]+)%?$/) {
$single_threshold = $1;
- ($1 >= 0 && $1 <= 100) or die($usage);
+ ($1 >= 0 && $1 <= 20) or die($usage);
# --auto=yes|no
} elsif ($arg =~ /^--auto=yes$/) {
@@ -377,7 +377,7 @@
# the primary sort event, and 0% for the rest.
if (not @thresholds) {
foreach my $e (@sort_order) {
- push(@thresholds, 0);
+ push(@thresholds, 100);
}
$thresholds[0] = $single_threshold;
}
@@ -617,17 +617,18 @@
# Print functions, stopping when the threshold has been reached.
foreach my $fn_name (@fn_fullnames) {
+ my $fn_CC = $fn_totals{$fn_name};
+
# Stop when we've reached all the thresholds
- my $reached_all_thresholds = 1;
+ my $any_thresholds_exceeded = 0;
foreach my $i (0 .. scalar @thresholds - 1) {
- my $prop = safe_div(abs($curr_totals[$i] * 100),
+ my $prop = safe_div(abs($fn_CC->[$sort_order[$i]] * 100),
abs($summary_CC->[$sort_order[$i]]));
- $reached_all_thresholds &&= ($prop >= $thresholds[$i]);
+ $any_thresholds_exceeded ||= ($prop >= $thresholds[$i]);
}
- last if $reached_all_thresholds;
+ last if not $any_thresholds_exceeded;
# Print function results
- my $fn_CC = $fn_totals{$fn_name};
print_CC($fn_CC, $fn_CC_col_widths);
print(" $fn_name\n");
diff --git a/cachegrind/cg_diff.in b/cachegrind/cg_diff.in
new file mode 100755
index 0000000..951066e
--- /dev/null
+++ b/cachegrind/cg_diff.in
@@ -0,0 +1,328 @@
+#! @PERL@
+
+##--------------------------------------------------------------------##
+##--- Cachegrind's differencer. cg_diff.in ---##
+##--------------------------------------------------------------------##
+
+# This file is part of Cachegrind, a Valgrind tool for cache
+# profiling programs.
+#
+# Copyright (C) 2002-2010 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.
+
+#----------------------------------------------------------------------------
+# This is a very cut-down and modified version of cg_annotate.
+#----------------------------------------------------------------------------
+
+use warnings;
+use strict;
+
+#----------------------------------------------------------------------------
+# Global variables
+#----------------------------------------------------------------------------
+
+# Version number
+my $version = "@VERSION@";
+
+# Usage message.
+my $usage = <<END
+usage: cg_diff [options] <cachegrind-out-file1> <cachegrind-out-file2>
+
+ options for the user, with defaults in [ ], are:
+ -h --help show this message
+ -v --version show version
+ --mod-filename=<expr> a Perl search-and-replace expression that is applied
+ to filenames, eg. --mod-filename='s/prog[0-9]/projN/'
+
+ cg_diff is Copyright (C) 2010-2010 Nicholas Nethercote.
+ and licensed under the GNU General Public License, version 2.
+ Bug reports, feedback, admiration, abuse, etc, to: njn\@valgrind.org.
+
+END
+;
+
+# --mod-filename expression
+my $mod_filename = undef;
+
+#-----------------------------------------------------------------------------
+# Argument and option handling
+#-----------------------------------------------------------------------------
+sub process_cmd_line()
+{
+ my ($file1, $file2) = (undef, undef);
+
+ for my $arg (@ARGV) {
+
+ if ($arg =~ /^-/) {
+ # --version
+ if ($arg =~ /^-v$|^--version$/) {
+ die("cg_diff-$version\n");
+
+ } elsif ($arg =~ /^--mod-filename=(.*)/) {
+ $mod_filename = $1;
+
+ } else { # -h and --help fall under this case
+ die($usage);
+ }
+
+ } elsif (not defined($file1)) {
+ $file1 = $arg;
+
+ } elsif (not defined($file2)) {
+ $file2 = $arg;
+
+ } else {
+ die($usage);
+ }
+ }
+
+ # Must have specified two input files.
+ if (not defined $file1 or not defined $file2) {
+ die($usage);
+ }
+
+ return ($file1, $file2);
+}
+
+#-----------------------------------------------------------------------------
+# Reading of input file
+#-----------------------------------------------------------------------------
+sub max ($$)
+{
+ my ($x, $y) = @_;
+ return ($x > $y ? $x : $y);
+}
+
+# Add the two arrays; any '.' entries are ignored. Two tricky things:
+# 1. If $a2->[$i] is undefined, it defaults to 0 which is what we want; we turn
+# off warnings to allow this. This makes things about 10% faster than
+# checking for definedness ourselves.
+# 2. We don't add an undefined count or a ".", even though it's value is 0,
+# because we don't want to make an $a2->[$i] that is undef become 0
+# unnecessarily.
+sub add_array_a_to_b ($$)
+{
+ my ($a, $b) = @_;
+
+ my $n = max(scalar @$a, scalar @$b);
+ $^W = 0;
+ foreach my $i (0 .. $n-1) {
+ $b->[$i] += $a->[$i] if (defined $a->[$i] && "." ne $a->[$i]);
+ }
+ $^W = 1;
+}
+
+sub sub_array_b_from_a ($$)
+{
+ my ($a, $b) = @_;
+
+ my $n = max(scalar @$a, scalar @$b);
+ $^W = 0;
+ foreach my $i (0 .. $n-1) {
+ $a->[$i] -= $b->[$i]; # XXX: doesn't handle '.' entries
+ }
+ $^W = 1;
+}
+
+# Add each event count to the CC array. '.' counts become undef, as do
+# missing entries (implicitly).
+sub line_to_CC ($$)
+{
+ my ($line, $numEvents) = @_;
+
+ my @CC = (split /\s+/, $line);
+ (@CC <= $numEvents) or die("Line $.: too many event counts\n");
+ return \@CC;
+}
+
+sub read_input_file($)
+{
+ my ($input_file) = @_;
+
+ open(INPUTFILE, "< $input_file")
+ || die "Cannot open $input_file for reading\n";
+
+ # Read "desc:" lines.
+ my $desc;
+ my $line;
+ while ($line = <INPUTFILE>) {
+ if ($line =~ s/desc:\s+//) {
+ $desc .= $line;
+ } else {
+ last;
+ }
+ }
+
+ # Read "cmd:" line (Nb: will already be in $line from "desc:" loop above).
+ ($line =~ s/^cmd:\s+//) or die("Line $.: missing command line\n");
+ my $cmd = $line;
+ chomp($cmd); # Remove newline
+
+ # Read "events:" line. We make a temporary hash in which the Nth event's
+ # value is N, which is useful for handling --show/--sort options below.
+ $line = <INPUTFILE>;
+ (defined $line && $line =~ s/^events:\s+//)
+ or die("Line $.: missing events line\n");
+ my @events = split(/\s+/, $line);
+ my $numEvents = scalar @events;
+
+ my $currFileName;
+ my $currFileFuncName;
+
+ my %CCs; # hash("$filename#$funcname" => CC array)
+ my $currCC = undef; # CC array
+
+ my $summaryCC;
+
+ # Read body of input file.
+ while (<INPUTFILE>) {
+ s/#.*$//; # remove comments
+ if (s/^(\d+)\s+//) {
+ my $CC = line_to_CC($_, $numEvents);
+ defined($currCC) || die;
+ add_array_a_to_b($CC, $currCC);
+
+ } elsif (s/^fn=(.*)$//) {
+ defined($currFileName) || die;
+ $currFileFuncName = "$currFileName#$1";
+ $currCC = $CCs{$currFileFuncName};
+ if (not defined $currCC) {
+ $currCC = [];
+ $CCs{$currFileFuncName} = $currCC;
+ }
+
+ } elsif (s/^fl=(.*)$//) {
+ $currFileName = $1;
+ if (defined $mod_filename) {
+ eval "\$currFileName =~ $mod_filename";
+ }
+ # Assume that a "fn=" line is followed by a "fl=" line.
+ $currFileFuncName = undef;
+
+ } elsif (s/^\s*$//) {
+ # blank, do nothing
+
+ } elsif (s/^summary:\s+//) {
+ $summaryCC = line_to_CC($_, $numEvents);
+ (scalar(@$summaryCC) == @events)
+ or die("Line $.: summary event and total event mismatch\n");
+
+ } else {
+ warn("WARNING: line $. malformed, ignoring\n");
+ }
+ }
+
+ # Check if summary line was present
+ if (not defined $summaryCC) {
+ die("missing final summary line, aborting\n");
+ }
+
+ close(INPUTFILE);
+
+ return ($cmd, \@events, \%CCs, $summaryCC);
+}
+
+#----------------------------------------------------------------------------
+# "main()"
+#----------------------------------------------------------------------------
+# Commands seen in the files. Need not match.
+my $cmd1;
+my $cmd2;
+
+# Events seen in the files. They must match.
+my $events1;
+my $events2;
+
+# Individual CCs, organised by filename/funcname/line_num.
+# hashref("$filename#$funcname", CC array)
+my $CCs1;
+my $CCs2;
+
+# Total counts for summary (an arrayref).
+my $summaryCC1;
+my $summaryCC2;
+
+#----------------------------------------------------------------------------
+# Read the input files
+#----------------------------------------------------------------------------
+my ($file1, $file2) = process_cmd_line();
+($cmd1, $events1, $CCs1, $summaryCC1) = read_input_file($file1);
+($cmd2, $events2, $CCs2, $summaryCC2) = read_input_file($file2);
+
+#----------------------------------------------------------------------------
+# Check the events match
+#----------------------------------------------------------------------------
+my $n = max(scalar @$events1, scalar @$events2);
+$^W = 0; # turn off warnings, because we might hit undefs
+foreach my $i (0 .. $n-1) {
+ ($events1->[$i] eq $events2->[$i]) || die "events don't match, aborting\n";
+}
+$^W = 1;
+
+#----------------------------------------------------------------------------
+# Do the subtraction: CCs2 -= CCs1
+#----------------------------------------------------------------------------
+while (my ($filefuncname, $CC1) = each(%$CCs1)) {
+ my $CC2 = $CCs2->{$filefuncname};
+ if (not defined $CC2) {
+ $CC2 = [];
+ sub_array_b_from_a($CC2, $CC1); # CC2 -= CC1
+ $CCs2->{$filefuncname} = $CC2;
+ } else {
+ sub_array_b_from_a($CC2, $CC1); # CC2 -= CC1
+ }
+}
+sub_array_b_from_a($summaryCC2, $summaryCC1);
+
+#----------------------------------------------------------------------------
+# Print the result, in CCs2
+#----------------------------------------------------------------------------
+print("desc: Files compared: $file1; $file2\n");
+print("cmd: $cmd1; $cmd2\n");
+print("events: ");
+for my $e (@$events1) {
+ print(" $e");
+}
+print("\n");
+
+while (my ($filefuncname, $CC) = each(%$CCs2)) {
+
+ my @x = split(/#/, $filefuncname);
+ (scalar @x == 2) || die;
+
+ print("fl=$x[0]\n");
+ print("fn=$x[1]\n");
+
+ print("0");
+ foreach my $n (@$CC) {
+ print(" $n");
+ }
+ print("\n");
+}
+
+print("summary:");
+foreach my $n (@$summaryCC2) {
+ print(" $n");
+}
+print("\n");
+
+##--------------------------------------------------------------------##
+##--- end ---##
+##--------------------------------------------------------------------##
diff --git a/cachegrind/docs/cg-manual.xml b/cachegrind/docs/cg-manual.xml
index e8ab23d..b312771 100644
--- a/cachegrind/docs/cg-manual.xml
+++ b/cachegrind/docs/cg-manual.xml
@@ -98,8 +98,10 @@
<para>Then, you need to run Cachegrind itself to gather the profiling
information, and then run cg_annotate to get a detailed presentation of that
information. As an optional intermediate step, you can use cg_merge to sum
-together the outputs of multiple Cachegrind runs, into a single file which
-you then use as the input for cg_annotate.</para>
+together the outputs of multiple Cachegrind runs into a single file which
+you then use as the input for cg_annotate. Alternatively, you can use
+cg_diff to difference the outputs of two Cachegrind runs into a signel file
+which you then use as the input for cg_annotate.</para>
<sect2 id="cg-manual.running-cachegrind" xreflabel="Running Cachegrind">
@@ -697,6 +699,85 @@
</sect2>
+<sect2 id="cg-manual.cg_diff" xreflabel="cg_diff">
+<title>Differencing Profiles with cg_diff</title>
+
+<para>
+cg_diff is a simple program which
+reads two profile files, as created by Cachegrind, finds the difference
+between them, and writes the results into another file in the same format.
+You can then examine the merged results using
+<computeroutput>cg_annotate <filename></computeroutput>, as
+described above. This is very useful if you want to measure how a change to
+a program affected its performance.
+</para>
+
+<para>
+cg_diff is invoked as follows:
+</para>
+
+<programlisting><![CDATA[
+cg_diff file1 file2]]></programlisting>
+
+<para>
+It reads and checks <computeroutput>file1</computeroutput>, then read
+and checks <computeroutput>file2</computeroutput>, then computes the
+difference (effectively <computeroutput>file1</computeroutput> -
+<computeroutput>file2</computeroutput>). The final results are written to
+standard output.</para>
+
+<para>
+Costs are summed on a per-function basis. Per-line costs are not summed,
+because doing so is too difficult. For example, consider differencing two
+profiles, one from a single-file program A, and one from the same program A
+where a single blank line was inserted at the top of the file. Every single
+per-line count has changed. In comparison, the per-function counts have not
+changed. The per-function count differences are still very useful for
+determining differences between programs. Note that because the result is
+the difference of two profiles, many of the counts will be negative; this
+indicates that the counts for the relevant function are fewer in the second
+version than those in the first version.</para>
+
+<para>
+cg_diff does not attempt to check
+that the input files come from runs of the same executable. It will
+happily merge together profile files from completely unrelated
+programs. It does however check that the
+<computeroutput>Events:</computeroutput> lines of all the inputs are
+identical, so as to ensure that the addition of costs makes sense.
+For example, it would be nonsensical for it to add a number indicating
+D1 read references to a number from a different file indicating L2
+write misses.</para>
+
+<para>
+A number of other syntax and sanity checks are done whilst reading the
+inputs. cg_diff will stop and
+attempt to print a helpful error message if any of the input files
+fail these checks.</para>
+
+<para>
+Sometimes you will want to compare Cachegrind profiles of two versions of a
+program that you have sitting side-by-side. For example, you might have
+<computeroutput>version1/prog.c</computeroutput> and
+<computeroutput>version2/prog.c</computeroutput>, where the second is
+slightly different to the first. A straight comparison of the two will not
+be useful -- because functions are qualified with filenames, a function
+<function>f</function> will be listed as
+<computeroutput>version1/prog.c:f</computeroutput> for the first version but
+<computeroutput>version2/prog.c:f</computeroutput> for the second
+version.</para>
+
+<para>
+When this happens, you can use the <option>--mod-filename</option> option.
+Its argument is a Perl search-and-replace expression that will be applied
+to all the filenames in both Cachegrind output files. It can be used to
+remove minor differences in filenames. For example, the option
+<option>--mod-filename='s/version[0-9]/versionN/'</option> will suffice for
+this case.</para>
+
+</sect2>
+
+
</sect1>
@@ -842,21 +923,21 @@
<varlistentry>
<term>
- <option><![CDATA[--threshold=X [default: 99%] ]]></option>
+ <option><![CDATA[--threshold=X [default: 0.1%] ]]></option>
</term>
<listitem>
<para>Sets the threshold for the function-by-function
- summary. Functions are shown that account for more than X%
- of the primary sort event. If auto-annotating, also affects
- which files are annotated.</para>
+ summary. A function is shown if it accounts for more than X%
+ of the counts for the primary sort event. If auto-annotating, also
+ affects which files are annotated.</para>
<para>Note: thresholds can be set for more than one of the
events by appending any events for the
<option>--sort</option> option with a colon
and a number (no spaces, though). E.g. if you want to see
- the functions that cover 99% of L2 read misses and 99% of L2
+ each function that covers more than 1% of L2 read misses or 1% of L2
write misses, use this option:</para>
- <para><option>--sort=D2mr:99,D2mw:99</option></para>
+ <para><option>--sort=D2mr:1,D2mw:1</option></para>
</listitem>
</varlistentry>
@@ -900,6 +981,49 @@
</sect1>
+<sect1 id="cg-manual.diffopts" xreflabel="cg_diff Command-line Options">
+<title>cg_diff Command-line Options</title>
+
+<!-- start of xi:include in the manpage -->
+<variablelist id="cg_diff.opts.list">
+
+ <varlistentry>
+ <term>
+ <option><![CDATA[-h --help ]]></option>
+ </term>
+ <listitem>
+ <para>Show the help message.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option><![CDATA[--version ]]></option>
+ </term>
+ <listitem>
+ <para>Show the version number.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option><![CDATA[--mod-filename=<expr> [default: none]]]></option>
+ </term>
+ <listitem>
+ <para>Specifies a Perl search-and-replace expression that is applied
+ to all filenames. Useful for removing minor differences in paths
+ between two different versions of a program that are sitting in
+ different directories.</para>
+ </listitem>
+ </varlistentry>
+
+</variablelist>
+<!-- end of xi:include in the manpage -->
+
+</sect1>
+
+
+
<sect1 id="cg-manual.acting-on"
xreflabel="Acting on Cachegrind's Information">
diff --git a/configure.in b/configure.in
index 70ceb3a..6559720 100644
--- a/configure.in
+++ b/configure.in
@@ -1827,6 +1827,7 @@
cachegrind/tests/Makefile
cachegrind/tests/x86/Makefile
cachegrind/cg_annotate
+ cachegrind/cg_diff
callgrind/Makefile
callgrind/callgrind_annotate
callgrind/callgrind_control