Home | History | Annotate | Line # | Download | only in contrib
      1      1.1  mrg #!/usr/bin/env python3
      2      1.1  mrg #
      3      1.1  mrg # Script to analyze results of our branch prediction heuristics
      4      1.1  mrg #
      5      1.1  mrg # This file is part of GCC.
      6      1.1  mrg #
      7      1.1  mrg # GCC is free software; you can redistribute it and/or modify it under
      8      1.1  mrg # the terms of the GNU General Public License as published by the Free
      9      1.1  mrg # Software Foundation; either version 3, or (at your option) any later
     10      1.1  mrg # version.
     11      1.1  mrg #
     12      1.1  mrg # GCC is distributed in the hope that it will be useful, but WITHOUT ANY
     13      1.1  mrg # WARRANTY; without even the implied warranty of MERCHANTABILITY or
     14      1.1  mrg # FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     15      1.1  mrg # for more details.
     16      1.1  mrg #
     17      1.1  mrg # You should have received a copy of the GNU General Public License
     18      1.1  mrg # along with GCC; see the file COPYING3.  If not see
     19      1.1  mrg # <http://www.gnu.org/licenses/>.  */
     20      1.1  mrg #
     21      1.1  mrg #
     22      1.1  mrg #
     23      1.1  mrg # This script is used to calculate two basic properties of the branch prediction
     24      1.1  mrg # heuristics - coverage and hitrate.  Coverage is number of executions
     25      1.1  mrg # of a given branch matched by the heuristics and hitrate is probability
     26      1.1  mrg # that once branch is predicted as taken it is really taken.
     27      1.1  mrg #
     28      1.1  mrg # These values are useful to determine the quality of given heuristics.
     29      1.1  mrg # Hitrate may be directly used in predict.def.
     30      1.1  mrg #
     31      1.1  mrg # Usage:
     32      1.1  mrg #  Step 1: Compile and profile your program.  You need to use -fprofile-generate
     33      1.1  mrg #    flag to get the profiles.
     34      1.1  mrg #  Step 2: Make a reference run of the intrumented application.
     35      1.1  mrg #  Step 3: Compile the program with collected profile and dump IPA profiles
     36      1.1  mrg #          (-fprofile-use -fdump-ipa-profile-details)
     37      1.1  mrg #  Step 4: Collect all generated dump files:
     38      1.1  mrg #          find . -name '*.profile' | xargs cat > dump_file
     39      1.1  mrg #  Step 5: Run the script:
     40      1.1  mrg #          ./analyze_brprob.py dump_file
     41      1.1  mrg #          and read results.  Basically the following table is printed:
     42      1.1  mrg #
     43      1.1  mrg # HEURISTICS                           BRANCHES  (REL)  HITRATE                COVERAGE  (REL)
     44      1.1  mrg # early return (on trees)                     3   0.2%  35.83% /  93.64%          66360   0.0%
     45      1.1  mrg # guess loop iv compare                       8   0.6%  53.35% /  53.73%       11183344   0.0%
     46      1.1  mrg # call                                       18   1.4%  31.95% /  69.95%       51880179   0.2%
     47      1.1  mrg # loop guard                                 23   1.8%  84.13% /  84.85%    13749065956  42.2%
     48      1.1  mrg # opcode values positive (on trees)          42   3.3%  15.71% /  84.81%     6771097902  20.8%
     49      1.1  mrg # opcode values nonequal (on trees)         226  17.6%  72.48% /  72.84%      844753864   2.6%
     50      1.1  mrg # loop exit                                 231  18.0%  86.97% /  86.98%     8952666897  27.5%
     51      1.1  mrg # loop iterations                           239  18.6%  91.10% /  91.10%     3062707264   9.4%
     52      1.1  mrg # DS theory                                 281  21.9%  82.08% /  83.39%     7787264075  23.9%
     53      1.1  mrg # no prediction                             293  22.9%  46.92% /  70.70%     2293267840   7.0%
     54      1.1  mrg # guessed loop iterations                   313  24.4%  76.41% /  76.41%    10782750177  33.1%
     55      1.1  mrg # first match                               708  55.2%  82.30% /  82.31%    22489588691  69.0%
     56      1.1  mrg # combined                                 1282 100.0%  79.76% /  81.75%    32570120606 100.0%
     57      1.1  mrg #
     58      1.1  mrg #
     59      1.1  mrg #  The heuristics called "first match" is a heuristics used by GCC branch
     60      1.1  mrg #  prediction pass and it predicts 55.2% branches correctly. As you can,
     61      1.1  mrg #  the heuristics has very good covertage (69.05%).  On the other hand,
     62      1.1  mrg #  "opcode values nonequal (on trees)" heuristics has good hirate, but poor
     63      1.1  mrg #  coverage.
     64      1.1  mrg 
     65      1.1  mrg import sys
     66      1.1  mrg import os
     67      1.1  mrg import re
     68      1.1  mrg import argparse
     69      1.1  mrg 
     70      1.1  mrg from math import *
     71      1.1  mrg 
     72      1.1  mrg counter_aggregates = set(['combined', 'first match', 'DS theory',
     73      1.1  mrg     'no prediction'])
     74  1.1.1.2  mrg hot_threshold = 10
     75      1.1  mrg 
     76      1.1  mrg def percentage(a, b):
     77      1.1  mrg     return 100.0 * a / b
     78      1.1  mrg 
     79      1.1  mrg def average(values):
     80      1.1  mrg     return 1.0 * sum(values) / len(values)
     81      1.1  mrg 
     82      1.1  mrg def average_cutoff(values, cut):
     83      1.1  mrg     l = len(values)
     84      1.1  mrg     skip = floor(l * cut / 2)
     85      1.1  mrg     if skip > 0:
     86      1.1  mrg         values.sort()
     87      1.1  mrg         values = values[skip:-skip]
     88      1.1  mrg     return average(values)
     89      1.1  mrg 
     90      1.1  mrg def median(values):
     91      1.1  mrg     values.sort()
     92      1.1  mrg     return values[int(len(values) / 2)]
     93      1.1  mrg 
     94  1.1.1.2  mrg class PredictDefFile:
     95  1.1.1.2  mrg     def __init__(self, path):
     96  1.1.1.2  mrg         self.path = path
     97  1.1.1.2  mrg         self.predictors = {}
     98  1.1.1.2  mrg 
     99  1.1.1.2  mrg     def parse_and_modify(self, heuristics, write_def_file):
    100  1.1.1.2  mrg         lines = [x.rstrip() for x in open(self.path).readlines()]
    101  1.1.1.2  mrg 
    102  1.1.1.2  mrg         p = None
    103  1.1.1.2  mrg         modified_lines = []
    104  1.1.1.3  mrg         for i, l in enumerate(lines):
    105  1.1.1.2  mrg             if l.startswith('DEF_PREDICTOR'):
    106  1.1.1.3  mrg                 next_line = lines[i + 1]
    107  1.1.1.3  mrg                 if l.endswith(','):
    108  1.1.1.3  mrg                     l += next_line
    109  1.1.1.2  mrg                 m = re.match('.*"(.*)".*', l)
    110  1.1.1.2  mrg                 p = m.group(1)
    111  1.1.1.2  mrg             elif l == '':
    112  1.1.1.2  mrg                 p = None
    113  1.1.1.2  mrg 
    114  1.1.1.2  mrg             if p != None:
    115  1.1.1.2  mrg                 heuristic = [x for x in heuristics if x.name == p]
    116  1.1.1.2  mrg                 heuristic = heuristic[0] if len(heuristic) == 1 else None
    117  1.1.1.2  mrg 
    118  1.1.1.2  mrg                 m = re.match('.*HITRATE \(([^)]*)\).*', l)
    119  1.1.1.2  mrg                 if (m != None):
    120  1.1.1.2  mrg                     self.predictors[p] = int(m.group(1))
    121  1.1.1.2  mrg 
    122  1.1.1.2  mrg                     # modify the line
    123  1.1.1.2  mrg                     if heuristic != None:
    124  1.1.1.2  mrg                         new_line = (l[:m.start(1)]
    125  1.1.1.2  mrg                             + str(round(heuristic.get_hitrate()))
    126  1.1.1.2  mrg                             + l[m.end(1):])
    127  1.1.1.2  mrg                         l = new_line
    128  1.1.1.2  mrg                     p = None
    129  1.1.1.2  mrg                 elif 'PROB_VERY_LIKELY' in l:
    130  1.1.1.2  mrg                     self.predictors[p] = 100
    131  1.1.1.2  mrg             modified_lines.append(l)
    132  1.1.1.2  mrg 
    133  1.1.1.2  mrg         # save the file
    134  1.1.1.2  mrg         if write_def_file:
    135  1.1.1.2  mrg             with open(self.path, 'w+') as f:
    136  1.1.1.2  mrg                 for l in modified_lines:
    137  1.1.1.2  mrg                     f.write(l + '\n')
    138  1.1.1.2  mrg class Heuristics:
    139  1.1.1.2  mrg     def __init__(self, count, hits, fits):
    140  1.1.1.2  mrg         self.count = count
    141  1.1.1.2  mrg         self.hits = hits
    142  1.1.1.2  mrg         self.fits = fits
    143  1.1.1.2  mrg 
    144      1.1  mrg class Summary:
    145      1.1  mrg     def __init__(self, name):
    146      1.1  mrg         self.name = name
    147  1.1.1.2  mrg         self.edges= []
    148  1.1.1.2  mrg 
    149  1.1.1.2  mrg     def branches(self):
    150  1.1.1.2  mrg         return len(self.edges)
    151  1.1.1.2  mrg 
    152  1.1.1.2  mrg     def hits(self):
    153  1.1.1.2  mrg         return sum([x.hits for x in self.edges])
    154  1.1.1.2  mrg 
    155  1.1.1.2  mrg     def fits(self):
    156  1.1.1.2  mrg         return sum([x.fits for x in self.edges])
    157  1.1.1.2  mrg 
    158  1.1.1.2  mrg     def count(self):
    159  1.1.1.2  mrg         return sum([x.count for x in self.edges])
    160  1.1.1.2  mrg 
    161  1.1.1.2  mrg     def successfull_branches(self):
    162  1.1.1.2  mrg         return len([x for x in self.edges if 2 * x.hits >= x.count])
    163      1.1  mrg 
    164      1.1  mrg     def get_hitrate(self):
    165  1.1.1.2  mrg         return 100.0 * self.hits() / self.count()
    166      1.1  mrg 
    167      1.1  mrg     def get_branch_hitrate(self):
    168  1.1.1.2  mrg         return 100.0 * self.successfull_branches() / self.branches()
    169      1.1  mrg 
    170      1.1  mrg     def count_formatted(self):
    171  1.1.1.2  mrg         v = self.count()
    172  1.1.1.2  mrg         for unit in ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']:
    173      1.1  mrg             if v < 1000:
    174      1.1  mrg                 return "%3.2f%s" % (v, unit)
    175      1.1  mrg             v /= 1000.0
    176      1.1  mrg         return "%.1f%s" % (v, 'Y')
    177      1.1  mrg 
    178  1.1.1.2  mrg     def count(self):
    179  1.1.1.2  mrg         return sum([x.count for x in self.edges])
    180  1.1.1.2  mrg 
    181  1.1.1.2  mrg     def print(self, branches_max, count_max, predict_def):
    182  1.1.1.2  mrg         # filter out most hot edges (if requested)
    183  1.1.1.2  mrg         self.edges = sorted(self.edges, reverse = True, key = lambda x: x.count)
    184  1.1.1.2  mrg         if args.coverage_threshold != None:
    185  1.1.1.2  mrg             threshold = args.coverage_threshold * self.count() / 100
    186  1.1.1.2  mrg             edges = [x for x in self.edges if x.count < threshold]
    187  1.1.1.2  mrg             if len(edges) != 0:
    188  1.1.1.2  mrg                 self.edges = edges
    189  1.1.1.2  mrg 
    190  1.1.1.2  mrg         predicted_as = None
    191  1.1.1.2  mrg         if predict_def != None and self.name in predict_def.predictors:
    192  1.1.1.2  mrg             predicted_as = predict_def.predictors[self.name]
    193  1.1.1.2  mrg 
    194      1.1  mrg         print('%-40s %8i %5.1f%% %11.2f%% %7.2f%% / %6.2f%% %14i %8s %5.1f%%' %
    195  1.1.1.2  mrg             (self.name, self.branches(),
    196  1.1.1.2  mrg                 percentage(self.branches(), branches_max),
    197      1.1  mrg                 self.get_branch_hitrate(),
    198      1.1  mrg                 self.get_hitrate(),
    199  1.1.1.2  mrg                 percentage(self.fits(), self.count()),
    200  1.1.1.2  mrg                 self.count(), self.count_formatted(),
    201  1.1.1.2  mrg                 percentage(self.count(), count_max)), end = '')
    202  1.1.1.2  mrg 
    203  1.1.1.2  mrg         if predicted_as != None:
    204  1.1.1.2  mrg             print('%12i%% %5.1f%%' % (predicted_as,
    205  1.1.1.2  mrg                 self.get_hitrate() - predicted_as), end = '')
    206  1.1.1.2  mrg         else:
    207  1.1.1.2  mrg             print(' ' * 20, end = '')
    208  1.1.1.2  mrg 
    209  1.1.1.2  mrg         # print details about the most important edges
    210  1.1.1.2  mrg         if args.coverage_threshold == None:
    211  1.1.1.2  mrg             edges = [x for x in self.edges[:100] if x.count * hot_threshold > self.count()]
    212  1.1.1.2  mrg             if args.verbose:
    213  1.1.1.2  mrg                 for c in edges:
    214  1.1.1.2  mrg                     r = 100.0 * c.count / self.count()
    215  1.1.1.2  mrg                     print(' %.0f%%:%d' % (r, c.count), end = '')
    216  1.1.1.2  mrg             elif len(edges) > 0:
    217  1.1.1.2  mrg                 print(' %0.0f%%:%d' % (100.0 * sum([x.count for x in edges]) / self.count(), len(edges)), end = '')
    218  1.1.1.2  mrg 
    219  1.1.1.2  mrg         print()
    220      1.1  mrg 
    221      1.1  mrg class Profile:
    222      1.1  mrg     def __init__(self, filename):
    223      1.1  mrg         self.filename = filename
    224      1.1  mrg         self.heuristics = {}
    225      1.1  mrg         self.niter_vector = []
    226      1.1  mrg 
    227      1.1  mrg     def add(self, name, prediction, count, hits):
    228      1.1  mrg         if not name in self.heuristics:
    229      1.1  mrg             self.heuristics[name] = Summary(name)
    230      1.1  mrg 
    231      1.1  mrg         s = self.heuristics[name]
    232      1.1  mrg 
    233      1.1  mrg         if prediction < 50:
    234      1.1  mrg             hits = count - hits
    235      1.1  mrg         remaining = count - hits
    236  1.1.1.2  mrg         fits = max(hits, remaining)
    237      1.1  mrg 
    238  1.1.1.2  mrg         s.edges.append(Heuristics(count, hits, fits))
    239      1.1  mrg 
    240      1.1  mrg     def add_loop_niter(self, niter):
    241      1.1  mrg         if niter > 0:
    242      1.1  mrg             self.niter_vector.append(niter)
    243      1.1  mrg 
    244      1.1  mrg     def branches_max(self):
    245  1.1.1.2  mrg         return max([v.branches() for k, v in self.heuristics.items()])
    246      1.1  mrg 
    247      1.1  mrg     def count_max(self):
    248  1.1.1.2  mrg         return max([v.count() for k, v in self.heuristics.items()])
    249      1.1  mrg 
    250  1.1.1.2  mrg     def print_group(self, sorting, group_name, heuristics, predict_def):
    251      1.1  mrg         count_max = self.count_max()
    252      1.1  mrg         branches_max = self.branches_max()
    253      1.1  mrg 
    254  1.1.1.2  mrg         sorter = lambda x: x.branches()
    255      1.1  mrg         if sorting == 'branch-hitrate':
    256      1.1  mrg             sorter = lambda x: x.get_branch_hitrate()
    257      1.1  mrg         elif sorting == 'hitrate':
    258      1.1  mrg             sorter = lambda x: x.get_hitrate()
    259      1.1  mrg         elif sorting == 'coverage':
    260      1.1  mrg             sorter = lambda x: x.count
    261      1.1  mrg         elif sorting == 'name':
    262      1.1  mrg             sorter = lambda x: x.name.lower()
    263      1.1  mrg 
    264  1.1.1.2  mrg         print('%-40s %8s %6s %12s %18s %14s %8s %6s %12s %6s %s' %
    265      1.1  mrg             ('HEURISTICS', 'BRANCHES', '(REL)',
    266  1.1.1.2  mrg             'BR. HITRATE', 'HITRATE', 'COVERAGE', 'COVERAGE', '(REL)',
    267  1.1.1.2  mrg             'predict.def', '(REL)', 'HOT branches (>%d%%)' % hot_threshold))
    268      1.1  mrg         for h in sorted(heuristics, key = sorter):
    269  1.1.1.2  mrg             h.print(branches_max, count_max, predict_def)
    270      1.1  mrg 
    271      1.1  mrg     def dump(self, sorting):
    272      1.1  mrg         heuristics = self.heuristics.values()
    273      1.1  mrg         if len(heuristics) == 0:
    274      1.1  mrg             print('No heuristics available')
    275      1.1  mrg             return
    276      1.1  mrg 
    277  1.1.1.2  mrg         predict_def = None
    278  1.1.1.2  mrg         if args.def_file != None:
    279  1.1.1.2  mrg             predict_def = PredictDefFile(args.def_file)
    280  1.1.1.2  mrg             predict_def.parse_and_modify(heuristics, args.write_def_file)
    281  1.1.1.2  mrg 
    282      1.1  mrg         special = list(filter(lambda x: x.name in counter_aggregates,
    283      1.1  mrg             heuristics))
    284      1.1  mrg         normal = list(filter(lambda x: x.name not in counter_aggregates,
    285      1.1  mrg             heuristics))
    286      1.1  mrg 
    287  1.1.1.2  mrg         self.print_group(sorting, 'HEURISTICS', normal, predict_def)
    288      1.1  mrg         print()
    289  1.1.1.2  mrg         self.print_group(sorting, 'HEURISTIC AGGREGATES', special, predict_def)
    290      1.1  mrg 
    291      1.1  mrg         if len(self.niter_vector) > 0:
    292      1.1  mrg             print ('\nLoop count: %d' % len(self.niter_vector)),
    293      1.1  mrg             print('  avg. # of iter: %.2f' % average(self.niter_vector))
    294      1.1  mrg             print('  median # of iter: %.2f' % median(self.niter_vector))
    295      1.1  mrg             for v in [1, 5, 10, 20, 30]:
    296      1.1  mrg                 cut = 0.01 * v
    297      1.1  mrg                 print('  avg. (%d%% cutoff) # of iter: %.2f'
    298      1.1  mrg                     % (v, average_cutoff(self.niter_vector, cut)))
    299      1.1  mrg 
    300      1.1  mrg parser = argparse.ArgumentParser()
    301      1.1  mrg parser.add_argument('dump_file', metavar = 'dump_file',
    302      1.1  mrg     help = 'IPA profile dump file')
    303      1.1  mrg parser.add_argument('-s', '--sorting', dest = 'sorting',
    304      1.1  mrg     choices = ['branches', 'branch-hitrate', 'hitrate', 'coverage', 'name'],
    305      1.1  mrg     default = 'branches')
    306  1.1.1.2  mrg parser.add_argument('-d', '--def-file', help = 'path to predict.def')
    307  1.1.1.2  mrg parser.add_argument('-w', '--write-def-file', action = 'store_true',
    308  1.1.1.2  mrg     help = 'Modify predict.def file in order to set new numbers')
    309  1.1.1.2  mrg parser.add_argument('-c', '--coverage-threshold', type = int,
    310  1.1.1.2  mrg     help = 'Ignore edges that have percentage coverage >= coverage-threshold')
    311  1.1.1.2  mrg parser.add_argument('-v', '--verbose', action = 'store_true', help = 'Print verbose informations')
    312      1.1  mrg 
    313      1.1  mrg args = parser.parse_args()
    314      1.1  mrg 
    315  1.1.1.2  mrg profile = Profile(args.dump_file)
    316      1.1  mrg loop_niter_str = ';;  profile-based iteration count: '
    317  1.1.1.2  mrg 
    318  1.1.1.2  mrg for l in open(args.dump_file):
    319  1.1.1.2  mrg     if l.startswith(';;heuristics;'):
    320  1.1.1.2  mrg         parts = l.strip().split(';')
    321  1.1.1.2  mrg         assert len(parts) == 8
    322  1.1.1.2  mrg         name = parts[3]
    323  1.1.1.2  mrg         prediction = float(parts[6])
    324  1.1.1.2  mrg         count = int(parts[4])
    325  1.1.1.2  mrg         hits = int(parts[5])
    326      1.1  mrg 
    327      1.1  mrg         profile.add(name, prediction, count, hits)
    328      1.1  mrg     elif l.startswith(loop_niter_str):
    329      1.1  mrg         v = int(l[len(loop_niter_str):])
    330      1.1  mrg         profile.add_loop_niter(v)
    331      1.1  mrg 
    332      1.1  mrg profile.dump(args.sorting)
    333