diff --git a/.github/workflows/perf.yml b/.github/workflows/perf.yml index 207d5949448e..2c5e817f4cee 100644 --- a/.github/workflows/perf.yml +++ b/.github/workflows/perf.yml @@ -98,6 +98,14 @@ jobs: build_with_profile head deactivate + # The vibe.d workload is pinned by dub.selections.json, so a cache hit + # keeps the package registry out of the loop on all but the first run. + - name: Cache vibe.d workload sources + uses: actions/cache@v4 + with: + path: ~/.dub/packages + key: dub-${{ hashFiles('tools/perfrunner/workloads/vibed/dub.selections.json') }} + - name: Measure run: | set -uexo pipefail diff --git a/tools/perfrunner/.gitignore b/tools/perfrunner/.gitignore index f89d9b4adda1..8cbf8f5bf3d5 100644 --- a/tools/perfrunner/.gitignore +++ b/tools/perfrunner/.gitignore @@ -5,3 +5,6 @@ perfrunner.exe *.o *.obj results.json + +# The workload's dependency pin has to be tracked, the repo root ignores it. +!workloads/vibed/dub.selections.json diff --git a/tools/perfrunner/source/app.d b/tools/perfrunner/source/app.d index 98830374a4f6..8e2355f5a237 100644 --- a/tools/perfrunner/source/app.d +++ b/tools/perfrunner/source/app.d @@ -7,9 +7,16 @@ import std.stdio : stderr, writeln; import metrics : measure, initials; import report : MetricResult, render, Report; +import vibed : describeFlags; -// Initial workload: the one source file compile to measure DMD. -enum workload = buildPath(__FILE_FULL_PATH__.dirName.dirName, "workloads", "hello.d"); +enum workloads = buildPath(__FILE_FULL_PATH__.dirName.dirName, "workloads"); + +// The one source file compile to measure DMD. +enum workload = buildPath(workloads, "hello.d"); + +// Third-party workload: its sources are fetched by dub, the recipe is pinned in-repo. +enum vibedDir = buildPath(workloads, "vibed"); +enum vibedRoot = buildPath(vibedDir, "source", "app.d"); version (unittest) {} else int main(string[] args) @@ -50,8 +57,11 @@ int main(string[] args) auto tmp = buildPath(tempDir, "perfrunner"); mkdirRecurse(tmp); - auto base = measure(baseDmd, workload, basePhobos, tmp, "base"); - auto head = measure(headDmd, workload, headPhobos, tmp, "head"); + // Resolved once: both refs have to compile vibe.d with the same flags. + auto vibedFlags = describeFlags(vibedDir, baseDmd); + + auto base = measure(baseDmd, workload, basePhobos, vibedRoot, vibedFlags, tmp, "base"); + auto head = measure(headDmd, workload, headPhobos, vibedRoot, vibedFlags, tmp, "head"); MetricResult[] metrics; foreach (def; initials) diff --git a/tools/perfrunner/source/metrics.d b/tools/perfrunner/source/metrics.d index f22cdcdf5690..9ff93d1dcb46 100644 --- a/tools/perfrunner/source/metrics.d +++ b/tools/perfrunner/source/metrics.d @@ -22,26 +22,34 @@ immutable MetricDef[] initials = [ MetricDef("compile_hello_debug_instr", "compile hello.d (instr)", "count", "cachegrind"), MetricDef("compile_hello_release_instr", "compile hello.d -O (instr)", "count", "cachegrind"), MetricDef("compile_phobos_instr", "compile Phobos (instr)", "count", "cachegrind"), + MetricDef("compile_vibed_instr", "compile vibe.d (instr)", "count", "cachegrind"), 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"), MetricDef("phobos_max_rss", "peak RSS (compile Phobos)", "kb", "time -v"), + MetricDef("vibed_max_rss", "peak RSS (compile vibe.d)", "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 phobos, string tmp, string tag) +long[string] measure(string dmd, string workload, string phobos, + string vibed, string[] vibedFlags, string tmp, string tag) { auto stdPackage = buildPath(phobos, "std", "package.d"); auto phobosFlags = ["-i=std", "-preview=dip1000"]; + // Unlike a dub build, which compiles one package per invocation, this pulls + // the whole vibe.d tree into a single compile. + auto vibeFlags = "-i" ~ vibedFlags; return [ "compile_hello_debug_instr": instructions(dmd, [], workload, tmp, tag ~ "-dbg"), "compile_hello_release_instr": instructions(dmd, ["-O", "-release"], workload, tmp, tag ~ "-rel"), "compile_phobos_instr": instructions(dmd, phobosFlags, stdPackage, tmp, tag ~ "-phobos"), + "compile_vibed_instr": instructions(dmd, vibeFlags, vibed, tmp, tag ~ "-vibed"), "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), "phobos_max_rss": maxRss(dmd, phobosFlags, stdPackage, tmp, tag ~ "-phobos"), + "vibed_max_rss": maxRss(dmd, vibeFlags, vibed, tmp, tag ~ "-vibed"), ]; } diff --git a/tools/perfrunner/source/vibed.d b/tools/perfrunner/source/vibed.d new file mode 100644 index 000000000000..51ef120b95ee --- /dev/null +++ b/tools/perfrunner/source/vibed.d @@ -0,0 +1,58 @@ +module vibed; + +import std.process : execute; + +// The flags dub would pass to the compiler for the vibe.d workload: the +// -version=Have_* set and the import paths of all pinned dependencies. +// Called once, so base and head compile with an identical command line. +string[] describeFlags(string dir, string dmd) +{ + auto r = execute(["dub", "describe", "--root=" ~ dir, "--compiler=" ~ dmd, + "--data=versions,import-paths,string-import-paths"]); + if (r.status != 0) + throw new Exception("dub describe failed:\n" ~ r.output); + return splitFlags(r.output); +} + +// dub quotes any flag holding a space, so splitting on whitespace is not enough. +string[] splitFlags(string output) +{ + string[] flags; + char[] flag; + bool quoted, started; + + foreach (c; output) + { + if (c == '\'') + quoted = !quoted; + else if (!quoted && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) + { + if (started) + flags ~= flag.idup; + flag.length = 0; + started = false; + continue; + } + else + flag ~= c; + started = true; + } + + if (started) + flags ~= flag.idup; + return flags; +} + +unittest +{ + auto sample = "-version=Have_vibe_d -version=EventcoreEpollDriver " + ~ "'-I/home/me/my dub/packages/vibe-d/0.10.3/vibe-d/source/' " + ~ "-I/home/me/.dub/packages/eventcore/0.9.39/eventcore/source/\n"; + + assert(splitFlags(sample) == [ + "-version=Have_vibe_d", + "-version=EventcoreEpollDriver", + "-I/home/me/my dub/packages/vibe-d/0.10.3/vibe-d/source/", + "-I/home/me/.dub/packages/eventcore/0.9.39/eventcore/source/", + ]); +} diff --git a/tools/perfrunner/workloads/vibed/dub.json b/tools/perfrunner/workloads/vibed/dub.json new file mode 100644 index 000000000000..6b5f1cb257fc --- /dev/null +++ b/tools/perfrunner/workloads/vibed/dub.json @@ -0,0 +1,8 @@ +{ + "name": "vibed-workload", + "description": "vibe.d compile workload for perfrunner", + "targetType": "executable", + "dependencies": { + "vibe-d": "0.10.3" + } +} diff --git a/tools/perfrunner/workloads/vibed/dub.selections.json b/tools/perfrunner/workloads/vibed/dub.selections.json new file mode 100644 index 000000000000..cfaf2c7a6eb1 --- /dev/null +++ b/tools/perfrunner/workloads/vibed/dub.selections.json @@ -0,0 +1,21 @@ +{ + "fileVersion": 1, + "versions": { + "diet-ng": "1.8.4", + "during": "0.3.0", + "eventcore": "0.9.39", + "mir-linux-kernel": "1.2.1", + "openssl": "3.4.0", + "openssl-static": "1.0.5+3.0.8", + "silly": "1.1.1", + "stdx-allocator": "2.77.5", + "taggedalgebraic": "1.0.1", + "vibe-container": "1.7.1", + "vibe-core": "2.14.0", + "vibe-d": "0.10.3", + "vibe-http": "1.5.1", + "vibe-inet": "1.3.1", + "vibe-serialization": "1.2.0", + "vibe-stream": "1.4.1" + } +} diff --git a/tools/perfrunner/workloads/vibed/source/app.d b/tools/perfrunner/workloads/vibed/source/app.d new file mode 100644 index 000000000000..f38e3a3198ca --- /dev/null +++ b/tools/perfrunner/workloads/vibed/source/app.d @@ -0,0 +1,5 @@ +import vibe.vibe; + +void main() +{ +}