From 96ddc7248142ea6efabd0bbed51a10d9c1d74685 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 5 Nov 2024 01:23:01 -0600 Subject: [PATCH] scripts: Moved hot path calculation before recursive rendering So now the hot path participates in sorting, folding, etc: $ ./scripts/stack.py ./lfs.ci ./lfs_util.ci \ -Dfunction=lfsr_mount -t -sframe function frame limit lfsr_mount 96 2736 |-> lfsr_mdir_commit 512 2368 |-> lfsr_btree_commit__.constprop 336 1648 |-> lfs_alloc 272 1296 |-> lfsr_btree_commit 208 1856 |-> lfsr_btree_lookupnext_ 208 720 |-> lfsr_mtree_gc 192 2560 |-> lfsr_mtree_traverse 176 1024 |-> lfsr_rbyd_lookupnext 160 448 |-> lfsr_bd_readtag.constprop 128 288 |-> lfsr_mtree_lookup 128 848 |-> lfsr_bd_read 80 160 |-> lfsr_bd_read__ 80 80 |-> lfsr_fs_gc 80 2640 |-> lfsr_rbyd_sublookup 64 512 '-> lfsr_rbyd_alloc 16 1312 TOTAL 96 2736 This risks some rather unintuitive behavior now that the hot path rendering no longer matches the call stack, but in theory the extra sorting features are more useful? This is a bit of an experiment, if this is more confusing than useful, we can always revert to the strict call-order ordering. Note that you can _usually_ get the call-order ordering by sorting by limit, but this trick breaks if any call frames are zero sized... --- scripts/perf.py | 133 +++++++++++++++++++--------------------------- scripts/perfbd.py | 133 +++++++++++++++++++--------------------------- scripts/stack.py | 133 +++++++++++++++++++--------------------------- 3 files changed, 162 insertions(+), 237 deletions(-) diff --git a/scripts/perf.py b/scripts/perf.py index 023453d9..d3bce0d0 100755 --- a/scripts/perf.py +++ b/scripts/perf.py @@ -667,7 +667,7 @@ def table(Result, results, diff_results=None, *, all=False, percent=False, depth=None, - hot=False, + hot=None, **_): all_, all = all, __builtins__.all @@ -682,6 +682,33 @@ def table(Result, results, diff_results=None, *, if diff_results is not None: diff_results = fold(Result, diff_results, by=by) + # reduce children to hot paths? + if hot: + def rec_hot(results_, seen=set()): + if not results_: + return [] + + r = max(results_, + key=lambda r: tuple( + tuple( + (getattr(r, k),) + if getattr(r, k, None) is not None + else () + for k in ([k] if k else [ + k for k in Result._sort if k in fields]) + if k in fields) + for k in it.chain(hot, [None]))) + + # found a cycle? + if tuple(getattr(r, k) for k in Result._by) in seen: + return [] + + return [r._replace(children=[])] + rec_hot( + r.children, + seen | {tuple(getattr(r, k) for k in Result._by)}) + + results = [r._replace(children=rec_hot(r.children)) for r in results] + # organize by name table = { ','.join(str(getattr(r, k) or '') for k in by): r @@ -791,37 +818,32 @@ def table(Result, results, diff_results=None, *, getattr(diff_r, k, None))))) return entry - # recursive entry helpers - if hot: - def recurse(results_, depth_, seen=set(), - prefixes=('', '', '', '')): - # build the children table at each layer - results_ = fold(Result, results_, by=by) - table_ = { - ','.join(str(getattr(r, k) or '') for k in by): r - for r in results_} - names_ = list(table_.keys()) - if not names_: - return + # recursive entry helper + def recurse(results_, depth_, seen=set(), + prefixes=('', '', '', '')): + # build the children table at each layer + results_ = fold(Result, results_, by=by) + table_ = { + ','.join(str(getattr(r, k) or '') for k in by): r + for r in results_} + names_ = list(table_.keys()) - # find the "hottest" path at each step, we use - # the sort field if requested, but ignore reversedness - name = max(names_, - key=lambda n: tuple( - tuple( - # make sure to use the rebuilt table + # sort the children layer + names_.sort() + if sort: + for k, reverse in reversed(sort): + names_.sort( + key=lambda n: tuple( (getattr(table_[n], k),) if getattr(table_.get(n), k, None) is not None else () for k in ([k] if k else [ - k for k in Result._sort if k in fields]) - if k in fields) - for k, reverse in it.chain( - sort or [], - [(None, False)]))) + k for k in Result._sort if k in fields])), + reverse=reverse ^ (not k or k in Result._fields)) + for i, name in enumerate(names_): r = table_[name] - is_last = not r.children + is_last = (i == len(names_)-1) line = table_entry(name, r) line = [x if isinstance(x, tuple) else (x, []) for x in line] @@ -834,7 +856,7 @@ def table(Result, results, diff_results=None, *, # found a cycle? if name in seen: - return + continue # recurse? if depth_ > 1: @@ -842,58 +864,10 @@ def table(Result, results, diff_results=None, *, r.children, depth_-1, seen | {name}, - prefixes) - - else: - def recurse(results_, depth_, seen=set(), - prefixes=('', '', '', '')): - # build the children table at each layer - results_ = fold(Result, results_, by=by) - table_ = { - ','.join(str(getattr(r, k) or '') for k in by): r - for r in results_} - names_ = list(table_.keys()) - - # sort the children layer - names_.sort() - if sort: - for k, reverse in reversed(sort): - names_.sort( - key=lambda n: tuple( - (getattr(table_[n], k),) - if getattr(table_.get(n), k, None) is not None - else () - for k in ([k] if k else [ - k for k in Result._sort if k in fields])), - reverse=reverse ^ (not k or k in Result._fields)) - - for i, name in enumerate(names_): - r = table_[name] - is_last = (i == len(names_)-1) - - line = table_entry(name, r) - line = [x if isinstance(x, tuple) else (x, []) for x in line] - # add prefixes - line[0] = (prefixes[0+is_last] + line[0][0], line[0][1]) - # add cycle detection - if name in seen: - line[-1] = (line[-1][0], line[-1][1] + ['cycle detected']) - lines.append(line) - - # found a cycle? - if name in seen: - continue - - # recurse? - if depth_ > 1: - recurse( - r.children, - depth_-1, - seen | {name}, - (prefixes[2+is_last] + "|-> ", - prefixes[2+is_last] + "'-> ", - prefixes[2+is_last] + "| ", - prefixes[2+is_last] + " ")) + (prefixes[2+is_last] + "|-> ", + prefixes[2+is_last] + "'-> ", + prefixes[2+is_last] + "| ", + prefixes[2+is_last] + " ")) # entries if not summary: @@ -1294,7 +1268,8 @@ if __name__ == "__main__": "find a cycle. Defaults to 0.") parser.add_argument( '-t', '--hot', - action='store_true', + nargs='?', + action='append', help="Show only the hot path for each function call.") parser.add_argument( '-A', '--annotate', diff --git a/scripts/perfbd.py b/scripts/perfbd.py index 5e862242..13de25bc 100755 --- a/scripts/perfbd.py +++ b/scripts/perfbd.py @@ -631,7 +631,7 @@ def table(Result, results, diff_results=None, *, all=False, percent=False, depth=None, - hot=False, + hot=None, **_): all_, all = all, __builtins__.all @@ -646,6 +646,33 @@ def table(Result, results, diff_results=None, *, if diff_results is not None: diff_results = fold(Result, diff_results, by=by) + # reduce children to hot paths? + if hot: + def rec_hot(results_, seen=set()): + if not results_: + return [] + + r = max(results_, + key=lambda r: tuple( + tuple( + (getattr(r, k),) + if getattr(r, k, None) is not None + else () + for k in ([k] if k else [ + k for k in Result._sort if k in fields]) + if k in fields) + for k in it.chain(hot, [None]))) + + # found a cycle? + if tuple(getattr(r, k) for k in Result._by) in seen: + return [] + + return [r._replace(children=[])] + rec_hot( + r.children, + seen | {tuple(getattr(r, k) for k in Result._by)}) + + results = [r._replace(children=rec_hot(r.children)) for r in results] + # organize by name table = { ','.join(str(getattr(r, k) or '') for k in by): r @@ -755,37 +782,32 @@ def table(Result, results, diff_results=None, *, getattr(diff_r, k, None))))) return entry - # recursive entry helpers - if hot: - def recurse(results_, depth_, seen=set(), - prefixes=('', '', '', '')): - # build the children table at each layer - results_ = fold(Result, results_, by=by) - table_ = { - ','.join(str(getattr(r, k) or '') for k in by): r - for r in results_} - names_ = list(table_.keys()) - if not names_: - return + # recursive entry helper + def recurse(results_, depth_, seen=set(), + prefixes=('', '', '', '')): + # build the children table at each layer + results_ = fold(Result, results_, by=by) + table_ = { + ','.join(str(getattr(r, k) or '') for k in by): r + for r in results_} + names_ = list(table_.keys()) - # find the "hottest" path at each step, we use - # the sort field if requested, but ignore reversedness - name = max(names_, - key=lambda n: tuple( - tuple( - # make sure to use the rebuilt table + # sort the children layer + names_.sort() + if sort: + for k, reverse in reversed(sort): + names_.sort( + key=lambda n: tuple( (getattr(table_[n], k),) if getattr(table_.get(n), k, None) is not None else () for k in ([k] if k else [ - k for k in Result._sort if k in fields]) - if k in fields) - for k, reverse in it.chain( - sort or [], - [(None, False)]))) + k for k in Result._sort if k in fields])), + reverse=reverse ^ (not k or k in Result._fields)) + for i, name in enumerate(names_): r = table_[name] - is_last = not r.children + is_last = (i == len(names_)-1) line = table_entry(name, r) line = [x if isinstance(x, tuple) else (x, []) for x in line] @@ -798,7 +820,7 @@ def table(Result, results, diff_results=None, *, # found a cycle? if name in seen: - return + continue # recurse? if depth_ > 1: @@ -806,58 +828,10 @@ def table(Result, results, diff_results=None, *, r.children, depth_-1, seen | {name}, - prefixes) - - else: - def recurse(results_, depth_, seen=set(), - prefixes=('', '', '', '')): - # build the children table at each layer - results_ = fold(Result, results_, by=by) - table_ = { - ','.join(str(getattr(r, k) or '') for k in by): r - for r in results_} - names_ = list(table_.keys()) - - # sort the children layer - names_.sort() - if sort: - for k, reverse in reversed(sort): - names_.sort( - key=lambda n: tuple( - (getattr(table_[n], k),) - if getattr(table_.get(n), k, None) is not None - else () - for k in ([k] if k else [ - k for k in Result._sort if k in fields])), - reverse=reverse ^ (not k or k in Result._fields)) - - for i, name in enumerate(names_): - r = table_[name] - is_last = (i == len(names_)-1) - - line = table_entry(name, r) - line = [x if isinstance(x, tuple) else (x, []) for x in line] - # add prefixes - line[0] = (prefixes[0+is_last] + line[0][0], line[0][1]) - # add cycle detection - if name in seen: - line[-1] = (line[-1][0], line[-1][1] + ['cycle detected']) - lines.append(line) - - # found a cycle? - if name in seen: - continue - - # recurse? - if depth_ > 1: - recurse( - r.children, - depth_-1, - seen | {name}, - (prefixes[2+is_last] + "|-> ", - prefixes[2+is_last] + "'-> ", - prefixes[2+is_last] + "| ", - prefixes[2+is_last] + " ")) + (prefixes[2+is_last] + "|-> ", + prefixes[2+is_last] + "'-> ", + prefixes[2+is_last] + "| ", + prefixes[2+is_last] + " ")) # entries if not summary: @@ -1255,7 +1229,8 @@ if __name__ == "__main__": "find a cycle. Defaults to 0.") parser.add_argument( '-t', '--hot', - action='store_true', + nargs='?', + action='append', help="Show only the hot path for each function call.") parser.add_argument( '-A', '--annotate', diff --git a/scripts/stack.py b/scripts/stack.py index 0c85b48b..cfd04dd8 100755 --- a/scripts/stack.py +++ b/scripts/stack.py @@ -316,7 +316,7 @@ def table(Result, results, diff_results=None, *, all=False, percent=False, depth=None, - hot=False, + hot=None, **_): all_, all = all, __builtins__.all @@ -331,6 +331,33 @@ def table(Result, results, diff_results=None, *, if diff_results is not None: diff_results = fold(Result, diff_results, by=by) + # reduce children to hot paths? + if hot: + def rec_hot(results_, seen=set()): + if not results_: + return [] + + r = max(results_, + key=lambda r: tuple( + tuple( + (getattr(r, k),) + if getattr(r, k, None) is not None + else () + for k in ([k] if k else [ + k for k in Result._sort if k in fields]) + if k in fields) + for k in it.chain(hot, [None]))) + + # found a cycle? + if tuple(getattr(r, k) for k in Result._by) in seen: + return [] + + return [r._replace(children=[])] + rec_hot( + r.children, + seen | {tuple(getattr(r, k) for k in Result._by)}) + + results = [r._replace(children=rec_hot(r.children)) for r in results] + # organize by name table = { ','.join(str(getattr(r, k) or '') for k in by): r @@ -440,37 +467,32 @@ def table(Result, results, diff_results=None, *, getattr(diff_r, k, None))))) return entry - # recursive entry helpers - if hot: - def recurse(results_, depth_, seen=set(), - prefixes=('', '', '', '')): - # build the children table at each layer - results_ = fold(Result, results_, by=by) - table_ = { - ','.join(str(getattr(r, k) or '') for k in by): r - for r in results_} - names_ = list(table_.keys()) - if not names_: - return + # recursive entry helper + def recurse(results_, depth_, seen=set(), + prefixes=('', '', '', '')): + # build the children table at each layer + results_ = fold(Result, results_, by=by) + table_ = { + ','.join(str(getattr(r, k) or '') for k in by): r + for r in results_} + names_ = list(table_.keys()) - # find the "hottest" path at each step, we use - # the sort field if requested, but ignore reversedness - name = max(names_, - key=lambda n: tuple( - tuple( - # make sure to use the rebuilt table + # sort the children layer + names_.sort() + if sort: + for k, reverse in reversed(sort): + names_.sort( + key=lambda n: tuple( (getattr(table_[n], k),) if getattr(table_.get(n), k, None) is not None else () for k in ([k] if k else [ - k for k in Result._sort if k in fields]) - if k in fields) - for k, reverse in it.chain( - sort or [], - [(None, False)]))) + k for k in Result._sort if k in fields])), + reverse=reverse ^ (not k or k in Result._fields)) + for i, name in enumerate(names_): r = table_[name] - is_last = not r.children + is_last = (i == len(names_)-1) line = table_entry(name, r) line = [x if isinstance(x, tuple) else (x, []) for x in line] @@ -483,7 +505,7 @@ def table(Result, results, diff_results=None, *, # found a cycle? if name in seen: - return + continue # recurse? if depth_ > 1: @@ -491,58 +513,10 @@ def table(Result, results, diff_results=None, *, r.children, depth_-1, seen | {name}, - prefixes) - - else: - def recurse(results_, depth_, seen=set(), - prefixes=('', '', '', '')): - # build the children table at each layer - results_ = fold(Result, results_, by=by) - table_ = { - ','.join(str(getattr(r, k) or '') for k in by): r - for r in results_} - names_ = list(table_.keys()) - - # sort the children layer - names_.sort() - if sort: - for k, reverse in reversed(sort): - names_.sort( - key=lambda n: tuple( - (getattr(table_[n], k),) - if getattr(table_.get(n), k, None) is not None - else () - for k in ([k] if k else [ - k for k in Result._sort if k in fields])), - reverse=reverse ^ (not k or k in Result._fields)) - - for i, name in enumerate(names_): - r = table_[name] - is_last = (i == len(names_)-1) - - line = table_entry(name, r) - line = [x if isinstance(x, tuple) else (x, []) for x in line] - # add prefixes - line[0] = (prefixes[0+is_last] + line[0][0], line[0][1]) - # add cycle detection - if name in seen: - line[-1] = (line[-1][0], line[-1][1] + ['cycle detected']) - lines.append(line) - - # found a cycle? - if name in seen: - continue - - # recurse? - if depth_ > 1: - recurse( - r.children, - depth_-1, - seen | {name}, - (prefixes[2+is_last] + "|-> ", - prefixes[2+is_last] + "'-> ", - prefixes[2+is_last] + "| ", - prefixes[2+is_last] + " ")) + (prefixes[2+is_last] + "|-> ", + prefixes[2+is_last] + "'-> ", + prefixes[2+is_last] + "| ", + prefixes[2+is_last] + " ")) # entries if not summary: @@ -798,7 +772,8 @@ if __name__ == "__main__": "find a cycle. Defaults to 0.") parser.add_argument( '-t', '--hot', - action='store_true', + nargs='?', + action='append', help="Show only the hot path for each function call.") parser.add_argument( '-e', '--error-on-recursion',