cc_bench_stages.py (6381B)
1 #!/usr/bin/env python3 2 """Attribute `sample` self-time to compiler STAGES for the cc benchmark. 3 4 Reads build/bench/hot/<axis>/raw/<axis>.sample.txt (a macOS `sample` call graph), 5 reconstructs flat self-time per function (same as cc_bench_report.py), then 6 buckets each function into a pipeline stage by name/image so we can see where the 7 time goes per stage rather than per function. 8 """ 9 import os 10 import re 11 import sys 12 from collections import defaultdict 13 14 FRAME_RE = re.compile(r"^(?P<indent>[ |+!:]*)(?P<count>\d+)\s+(?P<rest>.*\S)\s*$") 15 16 17 def parse_sample(text): 18 lines = text.splitlines() 19 start = None 20 for i, ln in enumerate(lines): 21 if ln.strip() == "Call graph:": 22 start = i + 1 23 break 24 if start is None: 25 return None, 0 26 indents, counts, child_sum, names, stack = [], [], [], [], [] 27 started = False 28 for ln in lines[start:]: 29 if not ln.strip(): 30 if started: 31 break 32 continue 33 m = FRAME_RE.match(ln) 34 if not m: 35 if started: 36 break 37 continue 38 started = True 39 indent = len(m.group("indent")) 40 cnt = int(m.group("count")) 41 rest = m.group("rest") 42 if " (in " in rest: 43 sym = rest.split(" (in ", 1)[0].strip() 44 img = rest.split(" (in ", 1)[1].split(")", 1)[0].strip() 45 else: 46 sym = rest.split(" ")[0].strip() 47 img = "" 48 idx = len(counts) 49 indents.append(indent); counts.append(cnt); child_sum.append(0) 50 names.append((sym, img)) 51 while stack and indent <= indents[stack[-1]]: 52 stack.pop() 53 if stack: 54 child_sum[stack[-1]] += cnt 55 stack.append(idx) 56 self_by = defaultdict(float) 57 total = 0.0 58 for i in range(len(counts)): 59 st = max(0, counts[i] - child_sum[i]) 60 sym, img = names[i] 61 if not sym: 62 continue 63 self_by[(sym, img)] += st 64 total += st 65 return self_by, total 66 67 68 # Stage classifier: ordered (first match wins). Each entry is (stage, predicate). 69 def classify(sym, img): 70 s = sym 71 if "libsystem" in img or "dyld" in img.lower() or img.endswith(".dylib"): 72 if "memset" in s or "memmove" in s or "bzero" in s or "memcpy" in s: 73 return "libc:mem" 74 if "write" in s or "read" in s or "mmap" in s or "munmap" in s or \ 75 "madvise" in s or "reclaim" in s or "fault" in s or "kevent" in s: 76 return "libc:io/vm" 77 return "libc:other" 78 if s.startswith("DYLD-STUB"): 79 return "libc:mem" if "mem" in s else "libc:other" 80 # kit stages by symbol name 81 LEX = ("lex_next", "peek", "bump", "scan_pp_number", "ucn_len", 82 "skip_ws_and_comments", "lex_intern", "lex_fold_splices", 83 "lex_sync_splices", "scan_quoted", "pp_number_is_float", 84 "matches_include", "lex_here", "is_alnum", "is_alpha", "lex_loc") 85 PP = ("pp_next", "src_next", "subst_phase", "substitute_body", "pp_emit", 86 "push_buf", "src_push", "hs_add", "hs_register", "expand_", "preexpand", 87 "read_invocation", "peek_for_invoke", "fetch_tok", "macro_lookup", 88 "do_define", "do_directive", "pp_", "fdw_write", "w_str", "tv_grow", 89 "tv_push", "kit_writer") 90 INTERN = ("pool_intern", "fnv1a", "sym_eq", "kit_sym_intern", "kit_sym_str", 91 "table_rehash", "pool_slice", "entries_grow") 92 TYPES = ("type_ptr", "type_func", "type_array", "type_qualified", 93 "type_unqual", "type_cg", "type_struct", "type_intern", "alloc_type", 94 "alloc_struct", "c_abi", "abi_", "cache_get", "type_record", 95 "type_compatible", "type_composite", "AbiInfoMap", "CgRecordMap", 96 "TypeInternSet", "type_kind", "same_record", "type_promoted", 97 "type_is_", "type_tag") 98 PARSE = ("parse_", "declare_function", "scope_", "reject_", "expect_", 99 "tag_", "external_func", "advance", "ident_kw", "SymEntryMap", 100 "TagEntryMap", "ExternalFuncMap", "make_local", "p_", "perr", 101 "decl_", "sem_") 102 CG = ("nd_", "api_", "cg_", "native_", "kit_cg", "pcg_", "fold", "value", 103 "arith", "abi_cg", "native_argmove", "make_sv") 104 EMIT = ("aa_", "aa64_", "buf_", "obj_", "m_label", "emit_macho", "emit_", 105 "mc_", "debug_emit", "debug_func", "wr_u32", "macho_", "elf_", 106 "Sections_", "reloc", "obj_patch", "obj_write", "obj_pos", 107 "section", "kit_debug", "x64_", "rv_") 108 ARENA = ("arena_", "kit_arena", "c_pool", "heap_libc", "Pool", "buf_init") 109 110 def has(prefixes): 111 return any(s == p or s.startswith(p) for p in prefixes) 112 113 if has(LEX): return "lexer" 114 if has(INTERN): return "intern" 115 if has(PP): return "pp" 116 if has(TYPES): return "types/abi" 117 if has(PARSE): return "parser" 118 if has(CG): return "codegen" 119 if has(EMIT): return "emit" 120 if has(ARENA): return "arena/alloc" 121 return "other:" + s 122 123 124 def main(): 125 root = sys.argv[1] if len(sys.argv) > 1 else "build/bench/hot" 126 axes = sys.argv[2:] if len(sys.argv) > 2 else sorted( 127 d for d in os.listdir(root) if os.path.isdir(os.path.join(root, d))) 128 for axis in axes: 129 raw = os.path.join(root, axis, "raw", axis + ".sample.txt") 130 if not os.path.exists(raw): 131 continue 132 text = open(raw, errors="replace").read() 133 if text.startswith("skipped:") or "Call graph:" not in text: 134 print(f"\n## {axis}: (no sample)") 135 continue 136 self_by, total = parse_sample(text) 137 if not self_by or total <= 0: 138 print(f"\n## {axis}: (unparseable)") 139 continue 140 stage = defaultdict(float) 141 topfn = defaultdict(list) 142 for (sym, img), v in self_by.items(): 143 st = classify(sym, img) 144 stage[st] += v 145 topfn[st].append((v, sym)) 146 print(f"\n## {axis} ({int(total)} samples)") 147 for st in sorted(stage, key=lambda k: stage[k], reverse=True): 148 pct = 100.0 * stage[st] / total 149 if pct < 0.5: 150 continue 151 tops = sorted(topfn[st], reverse=True)[:3] 152 tags = ", ".join("%s %.0f%%" % (n, 100.0 * c / total) for c, n in tops if c) 153 print(f" {pct:5.1f}% {st:14s} [{tags}]") 154 155 156 if __name__ == "__main__": 157 main()