diff --git a/.github/scripts/perf_comment.py b/.github/scripts/perf_comment.py index c4586927243e..659421ae0818 100644 --- a/.github/scripts/perf_comment.py +++ b/.github/scripts/perf_comment.py @@ -42,18 +42,29 @@ def is_significant(m): return abs(m["delta_pct"]) >= THRESHOLDS.get(m["method"], 0.1) -# Stage rows (frontend/backend) shown as base%/head% shares of their group. +# Stage rows (frontend/backend) shown as base%/head% shares with pp delta. def breakdown(children): base_total = sum(c["base"] for c in children) head_total = sum(c["head"] for c in children) if head_total <= 0: return [] - return ["| ↳ {} | {} | {} | {} |".format( - c["label"], - fmt_share(c["base"], base_total), - fmt_share(c["head"], head_total), - fmt_delta(c["delta_pct"]), - ) for c in children] + rows = [] + for c in children: + base_pct = c["base"] / base_total * 100 if base_total > 0 else 0 + head_pct = c["head"] / head_total * 100 if head_total > 0 else 0 + pp = head_pct - base_pct + if abs(pp) < 0.5: + delta_str = "~0pp" + else: + sign = "+" if pp > 0 else "" + delta_str = f"{sign}{pp:.0f}pp" + rows.append("| ↳ {} | {} | {} | {} |".format( + c["label"], + fmt_share(c["base"], base_total), + fmt_share(c["head"], head_total), + delta_str, + )) + return rows def render(results): diff --git a/tools/perfrunner/source/metrics.d b/tools/perfrunner/source/metrics.d index 96a62e22da07..888a980a91e1 100644 --- a/tools/perfrunner/source/metrics.d +++ b/tools/perfrunner/source/metrics.d @@ -30,6 +30,10 @@ immutable MetricDef[] initials = [ MetricDef("phobos_max_rss", "peak RSS (compile Phobos)", "kb", "time -v"), MetricDef("phobos_stage_frontend", "frontend (parse+sema)", "us", "time-trace", "compile_phobos_instr"), MetricDef("phobos_stage_backend", "backend (inline+codegen)", "us", "time-trace", "compile_phobos_instr"), + MetricDef("phobos_stage_parse", "parse", "us", "time-trace", "phobos_stage_frontend"), + MetricDef("phobos_stage_sema", "semantic analysis", "us", "time-trace", "phobos_stage_frontend"), + MetricDef("phobos_stage_inline", "inlining", "us", "time-trace", "phobos_stage_backend"), + MetricDef("phobos_stage_codegen", "code generation", "us", "time-trace", "phobos_stage_backend"), ]; // Measure every metric for one dmd binary. `tag` ("base"/"head") @@ -53,6 +57,10 @@ long[string] measure(string dmd, string workload, string phobos, string tmp, str auto st = stages(dmd, phobosFlags, stdPackage, tmp, tag ~ "-phobos"); m["phobos_stage_frontend"] = st.get("stage_frontend_us", 0); m["phobos_stage_backend"] = st.get("stage_backend_us", 0); + m["phobos_stage_parse"] = st.get("stage_parse_us", 0); + m["phobos_stage_sema"] = st.get("stage_sema_us", 0); + m["phobos_stage_inline"] = st.get("stage_inline_us", 0); + m["phobos_stage_codegen"] = st.get("stage_codegen_us", 0); return m; } diff --git a/tools/perfrunner/source/timetrace.d b/tools/perfrunner/source/timetrace.d index b637c5d15579..fa63708edf81 100644 --- a/tools/perfrunner/source/timetrace.d +++ b/tools/perfrunner/source/timetrace.d @@ -1,6 +1,6 @@ module timetrace; -import std.algorithm : canFind; + import std.file : readText; import std.json : parseJSON; import std.path : buildPath; @@ -9,24 +9,33 @@ import std.process : execute; // The top-level -ftime-trace spans grouped into frontend/backend. These names // match main.d's generic phase spans; they don't overlap, so summing them // partitions the compile without double-counting the nested per-symbol events. -private immutable string[] frontendSpans = ["Parsing", "Semantic analysis"]; -private immutable string[] backendSpans = ["Inlining", "Code generation"]; - // Sum the duration (microseconds) of the top-level spans in each bucket. +// Returns both coarse (frontend/backend) and fine (parse/sema/inline/codegen). long[string] parseStages(string trace) { - long frontend, backend; + long parse, sema, inline, codegen; foreach (e; parseJSON(trace)["traceEvents"].array) { if (e["ph"].str != "X") continue; auto name = e["name"].str; - if (frontendSpans.canFind(name)) - frontend += e["dur"].integer; - else if (backendSpans.canFind(name)) - backend += e["dur"].integer; + if (name == "Parsing") + parse += e["dur"].integer; + else if (name == "Semantic analysis") + sema += e["dur"].integer; + else if (name == "Inlining") + inline += e["dur"].integer; + else if (name == "Code generation") + codegen += e["dur"].integer; } - return ["stage_frontend_us": frontend, "stage_backend_us": backend]; + return [ + "stage_frontend_us": parse + sema, + "stage_backend_us": inline + codegen, + "stage_parse_us": parse, + "stage_sema_us": sema, + "stage_inline_us": inline, + "stage_codegen_us": codegen, + ]; } // Compile the workload with -ftime-trace and read the per-stage durations. @@ -57,4 +66,8 @@ unittest auto s = parseStages(sample); assert(s["stage_frontend_us"] == 400); assert(s["stage_backend_us"] == 100); + assert(s["stage_parse_us"] == 100); + assert(s["stage_sema_us"] == 300); + assert(s["stage_inline_us"] == 20); + assert(s["stage_codegen_us"] == 80); }