Skip to content

Commit b7278af

Browse files
committedMar 1, 2017
New tool: opt-stats.py
I am planning to use this tool to find too noisy (missed) optimization remarks. Long term it may actually be better to just have another tool that exports the remarks into an sqlite database and perform queries like this in SQL. This splits out the YAML parsing from opt-viewer.py into a new Python module optrecord.py. This is the result of the script on the LLVM testsuite: Total number of remarks 714433 Top 10 remarks by pass: inline 52% gvn 24% licm 13% loop-vectorize 5% asm-printer 3% loop-unroll 1% regalloc 1% inline-cost 0% slp-vectorizer 0% loop-delete 0% Top 10 remarks: gvn/LoadClobbered 20% inline/Inlined 19% inline/CanBeInlined 18% inline/NoDefinition 9% licm/LoadWithLoopInvariantAddressInvalidated 6% licm/Hoisted 6% asm-printer/InstructionCount 3% inline/TooCostly 3% gvn/LoadElim 3% loop-vectorize/MissedDetails 2% Beside some refactoring, I also changed optrecords not to use context to access global data (max_hotness). Because of the separate module this would have required splitting context into two. However it's not possible to access the optrecord context from the SourceFileRenderer when calling back to Remark.RelativeHotness. llvm-svn: 296682
1 parent 7329569 commit b7278af

File tree

3 files changed

+246
-188
lines changed

3 files changed

+246
-188
lines changed
 

‎llvm/utils/opt-viewer/opt-stats.py

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env python2.7
2+
3+
from __future__ import print_function
4+
5+
desc = '''Generate statistics about optimization records from the YAML files
6+
generated with -fsave-optimization-record and -fdiagnostics-show-hotness.
7+
8+
The tools requires PyYAML and Pygments Python packages.'''
9+
10+
import optrecord
11+
import argparse
12+
import operator
13+
from collections import defaultdict
14+
from multiprocessing import cpu_count, Pool
15+
16+
if __name__ == '__main__':
17+
parser = argparse.ArgumentParser(description=desc)
18+
parser.add_argument('yaml_files', nargs='+')
19+
parser.add_argument(
20+
'--jobs',
21+
'-j',
22+
default=cpu_count(),
23+
type=int,
24+
help='Max job count (defaults to current CPU count)')
25+
args = parser.parse_args()
26+
27+
if len(args.yaml_files) == 0:
28+
parser.print_help()
29+
sys.exit(1)
30+
31+
if args.jobs == 1:
32+
pmap = map
33+
else:
34+
pool = Pool(processes=args.jobs)
35+
pmap = pool.map
36+
37+
all_remarks, file_remarks, _ = optrecord.gather_results(pmap, args.yaml_files)
38+
39+
bypass = defaultdict(int)
40+
byname = defaultdict(int)
41+
for r in all_remarks.itervalues():
42+
bypass[r.Pass] += 1
43+
byname[r.Pass + "/" + r.Name] += 1
44+
45+
total = len(all_remarks)
46+
print("{:24s} {:10d}\n".format("Total number of remarks", total))
47+
48+
print("Top 10 remarks by pass:")
49+
for (passname, count) in sorted(bypass.items(), key=operator.itemgetter(1),
50+
reverse=True)[:10]:
51+
print(" {:30s} {:2.0f}%". format(passname, count * 100. / total))
52+
53+
print("\nTop 10 remarks:")
54+
for (name, count) in sorted(byname.items(), key=operator.itemgetter(1),
55+
reverse=True)[:10]:
56+
print(" {:30s} {:2.0f}%". format(name, count * 100. / total))

‎llvm/utils/opt-viewer/opt-viewer.py

+9-188
Original file line numberDiff line numberDiff line change
@@ -7,160 +7,28 @@
77
88
The tools requires PyYAML and Pygments Python packages.'''
99

10-
import yaml
11-
# Try to use the C parser.
12-
try:
13-
from yaml import CLoader as Loader
14-
except ImportError:
15-
print("For faster parsing, you may want to install libYAML for PyYAML")
16-
from yaml import Loader
17-
10+
import optrecord
1811
import functools
19-
from collections import defaultdict
20-
import itertools
2112
from multiprocessing import Pool
2213
from multiprocessing import Lock, cpu_count
2314
import errno
2415
import argparse
2516
import os.path
2617
import re
27-
import subprocess
2818
import shutil
2919
from pygments import highlight
3020
from pygments.lexers.c_cpp import CppLexer
3121
from pygments.formatters import HtmlFormatter
3222
import cgi
3323

34-
p = subprocess.Popen(['c++filt', '-n'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
35-
p_lock = Lock()
36-
37-
38-
def demangle(name):
39-
with p_lock:
40-
p.stdin.write(name + '\n')
41-
return p.stdout.readline().rstrip()
42-
4324
# This allows passing the global context to the child processes.
4425
class Context:
45-
def __init__(self, max_hotness = 0, caller_loc = dict()):
46-
self.max_hotness = max_hotness
47-
26+
def __init__(self, caller_loc = dict()):
4827
# Map function names to their source location for function where inlining happened
4928
self.caller_loc = caller_loc
5029

51-
def should_display_hotness(self):
52-
# If max_hotness is 0 at the end, we assume hotness information is
53-
# missing and no relative hotness information is displayed
54-
return self.max_hotness != 0
55-
5630
context = Context()
5731

58-
class Remark(yaml.YAMLObject):
59-
# Work-around for http://pyyaml.org/ticket/154.
60-
yaml_loader = Loader
61-
62-
def __getattr__(self, name):
63-
# If hotness is missing, assume 0
64-
if name == 'Hotness':
65-
return 0
66-
raise AttributeError
67-
68-
@property
69-
def File(self):
70-
return self.DebugLoc['File']
71-
72-
@property
73-
def Line(self):
74-
return int(self.DebugLoc['Line'])
75-
76-
@property
77-
def Column(self):
78-
return self.DebugLoc['Column']
79-
80-
@property
81-
def DebugLocString(self):
82-
return "{}:{}:{}".format(self.File, self.Line, self.Column)
83-
84-
@property
85-
def DemangledFunctionName(self):
86-
return demangle(self.Function)
87-
88-
@classmethod
89-
def make_link(cls, File, Line):
90-
return "{}#L{}".format(SourceFileRenderer.html_file_name(File), Line)
91-
92-
@property
93-
def Link(self):
94-
return Remark.make_link(self.File, self.Line)
95-
96-
def getArgString(self, mapping):
97-
mapping = mapping.copy()
98-
dl = mapping.get('DebugLoc')
99-
if dl:
100-
del mapping['DebugLoc']
101-
102-
assert(len(mapping) == 1)
103-
(key, value) = mapping.items()[0]
104-
105-
if key == 'Caller' or key == 'Callee':
106-
value = cgi.escape(demangle(value))
107-
108-
if dl and key != 'Caller':
109-
return "<a href={}>{}</a>".format(
110-
Remark.make_link(dl['File'], dl['Line']), value)
111-
else:
112-
return value
113-
114-
@property
115-
def message(self):
116-
# Args is a list of mappings (dictionaries)
117-
values = [self.getArgString(mapping) for mapping in self.Args]
118-
return "".join(values)
119-
120-
@property
121-
def RelativeHotness(self):
122-
if context.should_display_hotness():
123-
return "{}%".format(int(round(self.Hotness * 100 / context.max_hotness)))
124-
else:
125-
return ''
126-
127-
@property
128-
def key(self):
129-
return (self.__class__, self.Pass, self.Name, self.File, self.Line, self.Column, self.Function)
130-
131-
132-
class Analysis(Remark):
133-
yaml_tag = '!Analysis'
134-
135-
@property
136-
def color(self):
137-
return "white"
138-
139-
140-
class AnalysisFPCommute(Analysis):
141-
yaml_tag = '!AnalysisFPCommute'
142-
143-
144-
class AnalysisAliasing(Analysis):
145-
yaml_tag = '!AnalysisAliasing'
146-
147-
148-
class Passed(Remark):
149-
yaml_tag = '!Passed'
150-
151-
@property
152-
def color(self):
153-
return "green"
154-
155-
156-
class Missed(Remark):
157-
yaml_tag = '!Missed'
158-
159-
@property
160-
def color(self):
161-
return "red"
162-
163-
16432
class SourceFileRenderer:
16533
def __init__(self, source_dir, output_dir, filename):
16634
existing_filename = None
@@ -171,7 +39,7 @@ def __init__(self, source_dir, output_dir, filename):
17139
if os.path.exists(fn):
17240
existing_filename = fn
17341

174-
self.stream = open(os.path.join(output_dir, SourceFileRenderer.html_file_name(filename)), 'w')
42+
self.stream = open(os.path.join(output_dir, optrecord.html_file_name(filename)), 'w')
17543
if existing_filename:
17644
self.source_stream = open(existing_filename)
17745
else:
@@ -208,10 +76,9 @@ def render_source_lines(self, stream, line_remarks):
20876

20977
def render_inline_remarks(self, r, line):
21078
inlining_context = r.DemangledFunctionName
211-
print
21279
dl = context.caller_loc.get(r.Function)
21380
if dl:
214-
link = Remark.make_link(dl['File'], dl['Line'] - 2)
81+
link = optrecord.make_link(dl['File'], dl['Line'] - 2)
21582
inlining_context = "<a href={link}>{r.DemangledFunctionName}</a>".format(**locals())
21683

21784
# Column is the number of characters *including* tabs, keep those and
@@ -254,10 +121,6 @@ def render(self, line_remarks):
254121
</body>
255122
</html>''', file=self.stream)
256123

257-
@classmethod
258-
def html_file_name(cls, filename):
259-
return filename.replace('/', '_') + ".html"
260-
261124

262125
class IndexRenderer:
263126
def __init__(self, output_dir):
@@ -296,67 +159,25 @@ def render(self, all_remarks):
296159
</html>''', file=self.stream)
297160

298161

299-
def get_remarks(input_file):
300-
max_hotness = 0
301-
all_remarks = dict()
302-
file_remarks = defaultdict(functools.partial(defaultdict, list))
303-
304-
with open(input_file) as f:
305-
docs = yaml.load_all(f, Loader=Loader)
306-
307-
for remark in docs:
308-
# Avoid remarks withoug debug location or if they are duplicated
309-
if not hasattr(remark, 'DebugLoc') or remark.key in all_remarks:
310-
continue
311-
all_remarks[remark.key] = remark
312-
313-
file_remarks[remark.File][remark.Line].append(remark)
314-
315-
max_hotness = max(max_hotness, remark.Hotness)
316-
317-
return max_hotness, all_remarks, file_remarks
318-
319-
320162
def _render_file(source_dir, output_dir, ctx, entry):
321163
global context
322164
context = ctx
323165
filename, remarks = entry
324166
SourceFileRenderer(source_dir, output_dir, filename).render(remarks)
325167

326168

327-
def gather_results(pmap, filenames):
328-
remarks = pmap(get_remarks, filenames)
329-
330-
def merge_file_remarks(file_remarks_job, all_remarks, merged):
331-
for filename, d in file_remarks_job.iteritems():
332-
for line, remarks in d.iteritems():
333-
for remark in remarks:
334-
if remark.key not in all_remarks:
335-
merged[filename][line].append(remark)
336-
337-
all_remarks = dict()
338-
file_remarks = defaultdict(functools.partial(defaultdict, list))
339-
for _, all_remarks_job, file_remarks_job in remarks:
340-
merge_file_remarks(file_remarks_job, all_remarks, file_remarks)
341-
all_remarks.update(all_remarks_job)
342-
343-
context.max_hotness = max(entry[0] for entry in remarks)
344-
345-
return all_remarks, file_remarks
346-
347-
348169
def map_remarks(all_remarks):
349170
# Set up a map between function names and their source location for
350171
# function where inlining happened
351172
for remark in all_remarks.itervalues():
352-
if isinstance(remark, Passed) and remark.Pass == "inline" and remark.Name == "Inlined":
173+
if isinstance(remark, optrecord.Passed) and remark.Pass == "inline" and remark.Name == "Inlined":
353174
for arg in remark.Args:
354175
caller = arg.get('Caller')
355176
if caller:
356177
context.caller_loc[caller] = arg['DebugLoc']
357178

358179

359-
def generate_report(pmap, all_remarks, file_remarks, source_dir, output_dir):
180+
def generate_report(pmap, all_remarks, file_remarks, source_dir, output_dir, should_display_hotness):
360181
try:
361182
os.makedirs(output_dir)
362183
except OSError as e:
@@ -368,7 +189,7 @@ def generate_report(pmap, all_remarks, file_remarks, source_dir, output_dir):
368189
_render_file_bound = functools.partial(_render_file, source_dir, output_dir, context)
369190
pmap(_render_file_bound, file_remarks.items())
370191

371-
if context.should_display_hotness():
192+
if should_display_hotness:
372193
sorted_remarks = sorted(all_remarks.itervalues(), key=lambda r: (r.Hotness, r.__dict__), reverse=True)
373194
else:
374195
sorted_remarks = sorted(all_remarks.itervalues(), key=lambda r: (r.File, r.Line, r.Column, r.__dict__))
@@ -405,8 +226,8 @@ def generate_report(pmap, all_remarks, file_remarks, source_dir, output_dir):
405226
pool = Pool(processes=args.jobs)
406227
pmap = pool.map
407228

408-
all_remarks, file_remarks = gather_results(pmap, args.yaml_files)
229+
all_remarks, file_remarks, should_display_hotness = optrecord.gather_results(pmap, args.yaml_files)
409230

410231
map_remarks(all_remarks)
411232

412-
generate_report(pmap, all_remarks, file_remarks, args.source_dir, args.output_dir)
233+
generate_report(pmap, all_remarks, file_remarks, args.source_dir, args.output_dir, should_display_hotness)

0 commit comments

Comments
 (0)
Please sign in to comment.