diff --git a/.github/workflows/perf.yml b/.github/workflows/perf.yml index d34bf6a2333a..348959d32167 100644 --- a/.github/workflows/perf.yml +++ b/.github/workflows/perf.yml @@ -107,6 +107,8 @@ jobs: dub run -- \ --base-dmd "$RUNNER_TEMP/base/dmd/$built" \ --head-dmd "$RUNNER_TEMP/head/dmd/$built" \ + --base-phobos "$RUNNER_TEMP/base/phobos" \ + --head-phobos "$RUNNER_TEMP/head/phobos" \ --base-sha "${{ steps.refs.outputs.base }}" \ --head-sha "${{ steps.refs.outputs.head }}" \ --pr "${{ github.event.pull_request.number || 0 }}" \ diff --git a/compiler/src/dmd/root/aav.d b/compiler/src/dmd/root/aav.d index 381a97988b91..277bc128ca71 100644 --- a/compiler/src/dmd/root/aav.d +++ b/compiler/src/dmd/root/aav.d @@ -18,8 +18,28 @@ nothrow: private size_t hash(size_t a) pure nothrow @nogc @safe { - a ^= (a >> 20) ^ (a >> 12); - return a ^ (a >> 7) ^ (a >> 4); + // Keys are pointers: 16-byte aligned (low bits zero) and roughly sequential + // from the bump allocator, so they are highly correlated. The bucket index + // is `hash & (b_length - 1)`, i.e. the low bits, so the mix must spread + // entropy all the way down. This is the murmur3 fmix full-avalanche + // finalizer (fmix64 on 64-bit hosts, fmix32 on 32-bit). + static if (size_t.sizeof == 8) + { + a ^= a >> 33; + a *= 0xff51afd7ed558ccd; + a ^= a >> 33; + a *= 0xc4ceb9fe1a85ec53; + a ^= a >> 33; + } + else + { + a ^= a >> 16; + a *= 0x85ebca6b; + a ^= a >> 13; + a *= 0xc2b2ae35; + a ^= a >> 16; + } + return a; } private struct KeyValueTemplate(K,V) diff --git a/tools/perfrunner/source/app.d b/tools/perfrunner/source/app.d index 617995eb6a80..f9fa8e047639 100644 --- a/tools/perfrunner/source/app.d +++ b/tools/perfrunner/source/app.d @@ -15,6 +15,7 @@ version (unittest) {} else int main(string[] args) { string baseDmd, headDmd, baseSha, headSha, hostDmd; + string basePhobos, headPhobos; string os = "ubuntu-latest"; string outPath = "results.json"; long pr; @@ -22,6 +23,8 @@ int main(string[] args) auto help = getopt(args, "base-dmd", "path to the base (merge-base) dmd binary", &baseDmd, "head-dmd", "path to the head (PR) dmd binary", &headDmd, + "base-phobos", "path to the base phobos checkout (enables Phobos workload)", &basePhobos, + "head-phobos", "path to the head phobos checkout (enables Phobos workload)", &headPhobos, "base-sha", "base commit sha (metadata)", &baseSha, "head-sha", "head commit sha (metadata)", &headSha, "pr", "pull request number (metadata)", &pr, @@ -46,13 +49,14 @@ int main(string[] args) auto tmp = buildPath(tempDir, "perfrunner"); mkdirRecurse(tmp); - auto base = measure(baseDmd, workload, tmp, "base"); - auto head = measure(headDmd, workload, tmp, "head"); + auto base = measure(baseDmd, workload, basePhobos, tmp, "base"); + auto head = measure(headDmd, workload, headPhobos, tmp, "head"); MetricResult[] metrics; foreach (def; initials) - metrics ~= MetricResult(def.id, def.label, def.unit, def.method, - base[def.id], head[def.id]); + if (def.id in base && def.id in head) + metrics ~= MetricResult(def.id, def.label, def.unit, def.method, + base[def.id], head[def.id]); auto rep = Report(baseSha, "merge-base", headSha, pr, os, hostDmd, metrics); write(outPath, render(rep)); diff --git a/tools/perfrunner/source/cachegrind.d b/tools/perfrunner/source/cachegrind.d index c0f997bcff10..969e46b7a477 100644 --- a/tools/perfrunner/source/cachegrind.d +++ b/tools/perfrunner/source/cachegrind.d @@ -5,7 +5,7 @@ import std.array : split; import std.conv : to; import std.file : readText; import std.path : buildPath; -import std.process : execute; +import std.process : execute, Config; import std.string : lineSplitter, strip; long parseInstructions(string cgout) @@ -16,19 +16,25 @@ long parseInstructions(string cgout) throw new Exception("could not find 'summary:' line in cachegrind output"); } -// Compile the workload under cachegrind -long instructions(string dmd, string[] dflags, string workload, string tmp, string tag) +// Run `dmd args...` under cachegrind (optionally from `workDir`) and return +// the retired-instruction count. +long instructionsAt(string dmd, string[] args, string tmp, string tag, string workDir = null) { - auto obj = buildPath(tmp, tag ~ ".o"); auto cgOut = buildPath(tmp, tag ~ ".cgout"); - auto cmd = ["valgrind", "--tool=cachegrind", "--cachegrind-out-file=" ~ cgOut, - dmd, "-c"] ~ dflags ~ [workload, "-of=" ~ obj]; - auto r = execute(cmd); + auto cmd = ["valgrind", "--tool=cachegrind", "--cachegrind-out-file=" ~ cgOut, dmd] ~ args; + auto r = execute(cmd, null, Config.none, size_t.max, workDir); if (r.status != 0) throw new Exception("cachegrind failed:\n" ~ r.output); return parseInstructions(readText(cgOut)); } +// Compile the workload under cachegrind +long instructions(string dmd, string[] dflags, string workload, string tmp, string tag) +{ + auto obj = buildPath(tmp, tag ~ ".o"); + return instructionsAt(dmd, ["-c"] ~ dflags ~ [workload, "-of=" ~ obj], tmp, tag); +} + unittest { auto sample = "events: Ir\nfn=(1) main\n5 100\nsummary: 1234500000\n"; diff --git a/tools/perfrunner/source/metrics.d b/tools/perfrunner/source/metrics.d index ff9e5a9acf42..d84e06b90a97 100644 --- a/tools/perfrunner/source/metrics.d +++ b/tools/perfrunner/source/metrics.d @@ -5,9 +5,9 @@ import std.file : copy, exists, getSize, remove; import std.path : buildPath; import std.regex : ctRegex, matchFirst; -import std.process : execute; +import std.process : execute, Config; -import cachegrind : instructions; +import cachegrind : instructions, instructionsAt; struct MetricDef { @@ -24,19 +24,35 @@ immutable MetricDef[] initials = [ MetricDef("dmd_binary_size", "dmd binary size (stripped)", "bytes", "stat"), MetricDef("hello_binary_size", "hello binary size", "bytes", "stat"), MetricDef("hello_max_rss", "peak RSS (compile hello.d)", "kb", "time -v"), + // Symbol-heavy front-end workload: analyse all of Phobos without codegen. + MetricDef("compile_phobos_instr", "compile Phobos (instr)", "count", "cachegrind"), + MetricDef("phobos_max_rss", "peak RSS (Phobos)", "kb", "time -v"), ]; -// Measure every metric for one dmd binary. `tag` ("base"/"head") -// keeps the two runs' temp files apart -long[string] measure(string dmd, string workload, string tmp, string tag) +// dmd arguments for the Phobos workload, run with the phobos dir as CWD so the +// compiler's own dmd.conf resolves std/core imports. `-o-` keeps it front-end +// only (no backend/codegen), `-i=std` forces full semantic of every std module. +private immutable string[] phobosArgs = ["-o-", "-i=std", "-preview=dip1000", "std/package.d"]; + +// Measure every metric for one dmd binary. `tag` ("base"/"head") keeps the two +// runs' temp files apart. The Phobos metrics are only measured when `phobosDir` +// is given (so local hello-only runs still work). +long[string] measure(string dmd, string workload, string phobosDir, string tmp, string tag) { - return [ + long[string] m = [ "compile_hello_debug_instr": instructions(dmd, [], workload, tmp, tag ~ "-dbg"), "compile_hello_release_instr": instructions(dmd, ["-O", "-release"], workload, tmp, tag ~ "-rel"), "dmd_binary_size": strippedSize(dmd, buildPath(tmp, tag ~ "-dmd")), "hello_binary_size": helloSize(dmd, workload, tmp, tag), "hello_max_rss": maxRss(dmd, workload, tmp, tag), ]; + + if (phobosDir.length) + { + m["compile_phobos_instr"] = instructionsAt(dmd, phobosArgs.dup, tmp, tag ~ "-phobos", phobosDir); + m["phobos_max_rss"] = maxRssCmd([dmd] ~ phobosArgs.dup, phobosDir); + } + return m; } // Byte size of `binary` @@ -71,7 +87,13 @@ private void strip(string path) private long maxRss(string dmd, string workload, string tmp, string tag) { auto obj = buildPath(tmp, tag ~ "-rss.o"); - auto r = execute(["/usr/bin/time", "-v", dmd, "-c", workload, "-of=" ~ obj]); + return maxRssCmd([dmd, "-c", workload, "-of=" ~ obj]); +} + +// Peak RSS (KiB) of running `cmd` under /usr/bin/time, optionally from `workDir`. +private long maxRssCmd(string[] cmd, string workDir = null) +{ + auto r = execute(["/usr/bin/time", "-v"] ~ cmd, null, Config.none, size_t.max, workDir); if (r.status != 0) throw new Exception("/usr/bin/time failed:\n" ~ r.output); return parseMaxRss(r.output);