sclass MultiBencher { settable Runnable action; settable double seconds = 10; settable double interval = 0.001; double elapsedSeconds; new L benchers; *() {} *(Runnable *action) {} run { ensureCapacity(benchers, iceilRatio(seconds, interval)+2); long start = nanoTime(), endTime = lround(start+secondsToNanos(seconds)); while (nanoTime() < endTime) { var bencher = addAndReturn(benchers, new BenchForNSeconds(interval, action)); bencher.run(); } } long startTime() { ret empty(benchers) ? 0 : first(benchers).startTime; } S renderLegIndex(int i) { ret "Leg " + (i+1) + "/" + l(benchers) + " (T+" + n2(lround(nanosToMilliseconds(benchers.get(i).startTime-startTime()))) + " ms)"; } toString { ret lines(mapWithIndex(benchers, (i, b) -> renderLegIndex(i) + ": " + b)); } // "min records" are the times when a new minimum iteration time is reached S renderMinRecords(double threshold default 0.95) { new LS lines; long best = 0; for i, b over benchers: { long time = lround(b.min()); if (i == 0) { best = time; lines.add("First leg: " + n2(time) + " ns"); } else if (time < best*threshold) { best = time; lines.add("New best time in " + firstToLower(renderLegIndex(i)) + ": " + n2(time) + " ns"); } } ret lines(lines); } }