#!/usr/bin/env rdmd /** * This is a driver script that runs the benchmarks. * * Copyright: Copyright Martin Nowak 2011 -. * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Authors: Martin Nowak */ import std.stdio; extern(C) __gshared bool rt_cmdline_enabled = false; struct Config { string pattern = r".*\.d", dmd = "dmd", dflags = "-mcpu=native -O -release -inline", args; bool help, verbose, compile = true; uint repeat = 10; } string runCmd(string cmd, bool verbose, in char[] workDir = null) { import std.exception : enforce; import std.process : executeShell, Config; if (verbose) writeln(cmd); auto res = executeShell(cmd, null, Config.none, size_t.max, workDir); enforce(res.status == 0, res.output); return res.output; } string extraSourceOf(string path) { import std.path, std.string; string dir = path.dirName; while(dir != path) { string base = dir.baseName; if(base.endsWith(".extra")) return dir[0..$-6] ~ ".d"; path = dir; dir = path.dirName; } return null; } void runTests(Config cfg) { import std.algorithm, std.file, std.path, std.regex, std.string; if (exists("gcx.log")) remove("gcx.log"); string[] sources; string[string] extra_sources; auto re = regex(cfg.pattern, "g"); auto cwd = __FILE_FULL_PATH__.dirName; auto self = buildPath(cwd, "runbench.d"); foreach(DirEntry src; dirEntries(cwd, "*.d", SpanMode.depth)) { if (!src.isFile || src.name == self || src.name.withExtension(".ignore").exists) continue; string mainsrc = extraSourceOf(src.name); if (mainsrc) { if (cfg.verbose) writeln(src.name, " is extra file for ", mainsrc); extra_sources[mainsrc] ~= " " ~ src.name; } else if (!match(src.name, re).empty) sources ~= src.name; } import std.parallelism : parallel; immutable bindir = absolutePath("bin", cwd); immutable objdir = absolutePath("obj", cwd); foreach(ref src; sources.parallel(1)) { version (Windows) enum exe = "exe"; else enum exe = ""; auto bin = buildPath(bindir, src.relativePath(cwd).setExtension(exe)); auto obj = buildPath(objdir, src.relativePath(cwd).setExtension(exe)); auto cmd = std.string.format("%s %s -op -od%s -of%s %s", cfg.dmd, cfg.dflags, obj, bin, src); if (auto ex = src in extra_sources) cmd ~= " -I" ~ src[0..$-2] ~ ".extra" ~ *ex; if (cfg.compile) { writeln("COMPILING ", src); runCmd(cmd, cfg.verbose); } src = bin; } foreach(bin; sources) { import core.time : Duration; import std.algorithm : min; import std.datetime.stopwatch : AutoStart, StopWatch; auto sw = StopWatch(AutoStart.yes); auto minDur = Duration.max; string minGCProf; immutable benchName = bin.baseName.stripExtension; void report(string pfx, Duration dur, string gcProf) { auto parts = dur.split!("seconds", "msecs"); if (gcProf.length) writefln("%s %-16s %s.%03s s, %s", pfx, benchName, parts.seconds, parts.msecs, gcProf); else writefln("%s %-16s %s.%03s s", pfx, benchName, parts.seconds, parts.msecs); } auto cmd = bin ~ " " ~ cfg.args; foreach (_; 0 .. cfg.repeat) { sw.reset; auto output = runCmd(cmd, cfg.verbose, cwd); auto dur = cast(Duration)sw.peek; auto parts = dur.split!("seconds", "msecs"); if (cfg.verbose) stdout.write(output); string gcProf; if (exists("gcx.log")) { auto tgt = bin.setExtension("gcx.log"); rename("gcx.log", tgt); auto lines = File(tgt, "r").byLine() .find!(ln => ln.canFind("GC summary:")); if (!lines.empty) gcProf = lines.front.find("GC summary:")[11..$].idup; } else { auto lines = output.splitter(ctRegex!`\r\n|\r|\n`) .find!(ln => ln.startsWith("GC summary:")); if (!lines.empty) gcProf = lines.front[11..$]; } if (cfg.verbose) report("RUN", dur, gcProf); if (dur < minDur) { minDur = dur; minGCProf = gcProf; } } report("MIN", minDur, minGCProf); } } void printHelp() { import std.ascii : nl=newline; auto helpString = "usage: runbench [-h|--help] [-v|--verbose] [-r n|--repeat=n] [] [] [-- ]"~nl~nl~ " tests - Regular expressions to select tests. Default: '.*\\.d'"~nl~ " dflags - Flags passed to compiler. Default: '-mcpu=native -O -release -inline'"~nl~ " runargs - Arguments passed to each test, e.g. '--DRT-gcopt=profile=1'"~nl~nl~ "Don't pass any argument to run all tests with optimized builds."; writeln(helpString); } Config parseArgs(string[] args) { import std.algorithm, std.string : join; Config cfg; { import std.range : only; string[] tmp = args; if (findSkip(tmp, only("--"))) { import std.process : escapeShellCommand; cfg.args = escapeShellCommand(tmp); args = args[0 .. $ - 1 - tmp.length]; } } import std.getopt; getopt(args, config.stopOnFirstNonOption, config.passThrough, "h|help", &cfg.help, "v|verbose", &cfg.verbose, "N|no-compile", (string option) { cfg.compile = false; }, "dflags", &cfg.dflags, "r|repeat", &cfg.repeat); if (args.length >= 2 && !args[1].startsWith("-")) { cfg.pattern = args[1]; args = args.remove(1); } if (args.length > 1) cfg.dflags = join(args[1 .. $], " "); return cfg; } unittest { template cfg(N...) { static Config cfg(T...)(T vals) if (T.length == N.length) { Config res; foreach (i, ref v; vals) __traits(getMember, res, N[i]) = v; return res; } } import std.typecons : t=tuple; auto check = [ t(["bin"], cfg), t(["bin", "-h"], cfg!("help")(true)), t(["bin", "-v"], cfg!("verbose")(true)), t(["bin", "-h", "-v"], cfg!("help", "verbose")(true, true)), t(["bin", "gcbench"], cfg!("pattern")("gcbench")), t(["bin", "-v", "gcbench"], cfg!("pattern", "verbose")("gcbench", true)), t(["bin", "-r", "4", "gcbench"], cfg!("pattern", "repeat")("gcbench", 4)), t(["bin", "-g"], cfg!("dflags")("-g")), t(["bin", "gcbench", "-g"], cfg!("pattern", "dflags")("gcbench", "-g")), t(["bin", "-r", "2", "gcbench", "-g"], cfg!("pattern", "dflags", "repeat")("gcbench", "-g", 2)), t(["bin", "--", "--DRT-gcopt=profile:1"], cfg!("args")("--DRT-gcopt=profile:1")), t(["bin", "--", "foo", "bar"], cfg!("args")("foo bar")), t(["bin", "gcbench", "--", "args"], cfg!("pattern", "args")("gcbench", "args")), t(["bin", "--repeat=5", "gcbench", "--", "args"], cfg!("pattern", "args", "repeat")("gcbench", "args", 5)), ]; foreach (pair; check) assert(parseArgs(pair[0]) == pair[1]); } void main() { import core.runtime; // use Runtime.args for --DRT-gcopt auto cfg = parseArgs(Runtime.args); if (cfg.help) return printHelp(); import std.process : env=environment; cfg.dmd = env.get("DMD", "dmd"); if (cfg.compile) writeln("compiler: "~cfg.dmd~' '~cfg.dflags); runTests(cfg); }