Created
March 8, 2012 10:16
-
-
Save a2800276/2000141 to your computer and use it in GitHub Desktop.
straightforward Java port of Go (golang) Benchmark facility.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package benchmark; | |
import java.lang.reflect.*; | |
import java.util.*; | |
import java.util.regex.*; | |
abstract class InternalBenchmark { | |
String name; | |
abstract void runBenchmark (Benchmark b); | |
} | |
class BenchmarkResult { | |
int n; | |
long ns; | |
long bytes; | |
BenchmarkResult(int n, long ns, long bytes) { | |
this.n = n; | |
this.ns = ns; | |
this.bytes = bytes; | |
} | |
long nsPerOp() { | |
return this.n <=0 ? 0 : this.ns/this.n; | |
} | |
public String toString() { | |
long ns = this.nsPerOp(); | |
String mb = ""; | |
if (ns > 0 && this.bytes > 0) { | |
mb = String.format("\t%7.2f MB/s", (this.bytes / 1e6) / (ns/1e9)); | |
} | |
return String.format("%8d\t%10d ns/op%s", this.n, ns, mb); | |
} | |
} | |
/** | |
* Benchmark is a small utility to quickly and easily run simple | |
* Benchmarks on Java code. The code is based entirely on the benchmark | |
* utility from Go (see http://golang.org/pkg/testing/). | |
* | |
* In order to write benchmarks, you need to provide a class that has | |
* methods with signatures of the form: | |
* | |
* <pre> public void methodNameIrrelevant (Benchmark b) {...} </pre> | |
* | |
* The benchmarks in the class you wrote will be executed when the | |
* following command is run: | |
* | |
* <pre> java -jar Benchmark | |
* <name.of.class.containing.my.BenchmarkMethods> </pre> | |
* | |
* ( The above seems to have some classloader issues ... ) | |
* | |
* Alternatively, you can run benchmarks within Java code by calling: | |
* | |
* <pre> Benchmark.runBenchmark(MyBenchMark.class); </pre> | |
* | |
* The Benchmark utility will vary the number of times to run the | |
* benchmark until it runs long enough to be measured accurately. | |
* | |
* The number of times that a benchmark is supposed to run is signaled | |
* to your benchmark function by the field <code> N </code> in the | |
* <code>Benchmark</code> argument to the function. | |
* | |
* N.B. it is the responsibility of the benchmark function you provide | |
* to run the code <code>N</code> number of times and not up to the | |
* Benchmark utility! A typical benchmark function will look like this: | |
* | |
* <pre> | |
* public static void string (Benchmark b) { | |
* for (int n =0; n!= b.N; ++n) { | |
* String sum = ""; | |
* for (int i = 1; i!= 1000; ++i) { | |
* sum += i; | |
* } | |
* if (n == 0) { | |
* b.setBytes(sum.getBytes().length); | |
* } | |
* } | |
* } | |
* | |
* </pre> | |
* | |
* If <code>setBytes(...)</code> is called in the benchmark test, the | |
* benchmarks will display information about the throughput (MB/s) of the code | |
* as well as the the average time per operation. The value passed to | |
* <code>setBytes(...)</code> is the number of bytes processed in a | |
* single iteration, not how many bytes were processed in | |
* <code>b.N</code> loop iterations. | |
* | |
* In case the benchmark test requires lengthy setup that should be | |
* ignored, you can stop and restart the internal timer like this: | |
* | |
* <pre> | |
* public void myBenchmark (Benchmark b) { | |
* b.stopTimer(); | |
* reallyLengthySetup(); | |
* b.startTimer(); | |
* for (int i =0; i!= b.N, ++i) { | |
* ... whatever ... | |
* } | |
* } | |
* </pre> | |
* | |
* This class contains three sample benchmarks measuring the | |
* performance of string concatenation of <code>String</code>, | |
* <code>StringBuffer</code> and <code>StringBuilder</code>. These can | |
* be run as follows: | |
* | |
* <pre> | |
* $ java -jar lib/benchmark.jar benchmark.Benchmark | |
* string 500 6523698 ns/op 0.44 MB/s | |
* stringBuffer 50000 38646 ns/op 74.76 MB/s | |
* stringBuilder 50000 32237 ns/op 89.62 MB/s | |
* </pre> | |
* | |
* The results printed are: | |
* | |
* <ul> | |
* <li>name of the benchmark method</li> | |
* <li>number of times the utility ran the benchmark</li> | |
* <li>number of ns per loop</li> | |
* <li>number of MB processed per second</li> | |
* </ul> | |
* | |
* The tool may also be call as follows: | |
* | |
* <pre> | |
* $ java -jar lib/benchmark.jar benchmark.Benchmark stringB.* | |
* stringBuffer 50000 38646 ns/op 74.76 MB/s | |
* stringBuilder 50000 32237 ns/op 89.62 MB/s | |
* </pre> | |
* | |
* The final argument is an optional regular expression, if provided, | |
* only benchmark methods matching this expression are run. | |
* | |
* | |
* */ | |
public class Benchmark { | |
/** | |
* the number of times the Benchmark utility expects you to perform | |
* whatever it is you're benchmarking. | |
* | |
* A typical benchmark method will look like this: | |
* | |
* <pre> | |
* public void benchmarkMethod (Benchmark b) { | |
* for (int i = 0; i != b.N; ++i) { | |
* ... my benchmark code ... | |
* } | |
* } | |
* </pre> | |
* | |
* The responsibility of running the benchmark <code>N</code> is up to | |
* you, the the utility just tells you the value of N... | |
*/ | |
public int N; | |
InternalBenchmark internal; | |
long ns; | |
long bytes; | |
long start; | |
/** | |
* starts timing a test. This function is called automatically | |
* before a benchmark starts, but it can also used to resume timing after | |
* a call to StopTimer. | |
*/ | |
public void startTimer() { | |
this.start = System.nanoTime(); | |
} | |
/** | |
* stops timing a test. This can be used to pause the timer | |
* while performing complex initialization that you don't | |
* want to measure. | |
*/ | |
public void stopTimer() { | |
if (this.start > 0) { | |
this.ns += System.nanoTime() - this.start; | |
} | |
this.start = 0; | |
} | |
/** | |
* stops the timer and sets the elapsed benchmark time to zero. | |
*/ | |
public void resetTimer() { | |
this.start = 0; | |
this.ns = 0; | |
} | |
/** | |
* records the number of bytes processed in a single operation. | |
* If this is called, the benchmark will report ns/op and MB/s. | |
*/ | |
public void setBytes(long n) { | |
this.bytes = n; | |
} | |
long nsPerOp() { | |
if (this.N <= 0) { | |
return 0; | |
} | |
return this.ns / this.N; | |
} | |
// runN runs a single benchmark for the specified number of iterations. | |
void runN (int n) { | |
this.N = n; | |
this.resetTimer(); | |
this.startTimer(); | |
this.internal.runBenchmark(this); | |
this.stopTimer(); | |
} | |
static long min (long x, long y) { | |
return x > y ? y : x; | |
} | |
static long max (long x, long y) { | |
return x < y ? y : x; | |
} | |
// roundDown10 rounds a number down to the nearest power of 10. | |
static int roundDown10(int n) { | |
int tens = 0; | |
while (n > 10) { | |
n /= 10; | |
++tens; | |
} | |
int result = 1; | |
for (int i = 0; i< tens; ++i) { | |
result *= 10; | |
} | |
return result; | |
} | |
// roundUp rounds x up to a number of the form [1eX, 2eX, 5eX]. | |
static int roundUp(int n) { | |
int base = roundDown10(n); | |
if (n < (2*base)) { | |
return 2*base; | |
} | |
if (n < (5*base)){ | |
return 5*base; | |
} | |
return 10*base; | |
} | |
/** | |
* <code>run</code> times the benchmark function. It gradually increases the number | |
* of benchmark iterations until the benchmark runs for a second in order | |
* to get a reasonable measurement. It prints timing information in this form: | |
* | |
* <pre> | |
* testing.BenchmarkHello 100000 19 ns/op | |
* </pre> | |
*/ | |
public BenchmarkResult run () { | |
int n = 1; | |
// Run the benchmark for a single iteration in case it's expensive. | |
this.runN(n); | |
// Run the benchmark for at least a second. | |
while (this.ns < 1e9 && n<1e9){ | |
long last = n; | |
// Predict iterations/sec. | |
if (this.nsPerOp() == 0) { | |
n = (int)1e9; | |
} else { | |
n = (int)(1e9 / this.nsPerOp()); | |
} | |
// Run more iterations than we think we'll need for a second (1.5x). | |
// Don't grow too fast in case we had timing errors previously. | |
// Be sure to run at least one more than last time. | |
n = (int)max(min(n+n/2, 100*last), last+1); | |
// Round up to something easy to read. | |
n = roundUp(n); | |
this.runN(n); | |
} | |
return new BenchmarkResult(this.N, this.ns, this.bytes); | |
} | |
// | |
// Reflection stuff to load benchmarks | |
// | |
static boolean hasBenchmarkParam(Method m){ | |
Class [] paramTypes = m.getParameterTypes(); | |
if (paramTypes == null || paramTypes.length != 1) { | |
return false; | |
} | |
if (! paramTypes[0].equals(Benchmark.class) ) { | |
return false; | |
} | |
return true; | |
} | |
public static void runBenchmark(final Class c, String regexp) throws InstantiationException, IllegalAccessException { | |
Pattern pattern = null; | |
if (null != regexp) { | |
pattern = Pattern.compile(regexp); | |
} | |
List<Method> list = new ArrayList<Method>(); | |
int maxName = 0; | |
for (Method m : c.getMethods()){ | |
if (m.getReturnType() != Void.TYPE || !hasBenchmarkParam(m)) { | |
continue; | |
} | |
if (null != pattern) { | |
Matcher matcher = pattern.matcher(m.getName()); | |
if (!matcher.matches()) { | |
continue; | |
} | |
} | |
list.add(m); | |
maxName = m.getName().length() > maxName ? m.getName().length() : maxName; | |
} | |
for (final Method m : list) { | |
InternalBenchmark ib = new InternalBenchmark(){ | |
Object o = c.newInstance(); | |
void runBenchmark(Benchmark b) { | |
try { | |
m.invoke(o, b); | |
} catch (Throwable iae) { | |
throw new RuntimeException(iae); | |
} | |
} | |
}; | |
ib.name = m.getName(); | |
Benchmark b = new Benchmark(); | |
b.internal = ib; | |
BenchmarkResult res = b.run(); | |
System.out.println(String.format("%"+maxName+"s\t%s", ib.name, res)); | |
} | |
} | |
public static void runBenchmark(final Class c) throws InstantiationException, IllegalAccessException{ | |
runBenchmark(c, null); | |
} | |
// Test Benchmarks ... | |
/** | |
* Example: | |
* | |
* <pre> | |
* public void stringBuffer (Benchmark b) { | |
* for (int n =0; n!= b.N; ++n) { | |
* StringBuffer sum = new StringBuffer(); | |
* for (int i = 1; i!= 1000; ++i) { | |
* sum.append(i); | |
* } | |
* if (n == 0) { | |
* b.setBytes(sum.length()); | |
* } | |
* } | |
* } | |
* </pre> | |
*/ | |
public void stringBuffer (Benchmark b) { | |
for (int n =0; n!= b.N; ++n) { | |
StringBuffer sum = new StringBuffer(); | |
for (int i = 1; i!= 1000; ++i) { | |
sum.append(i); | |
} | |
if (n == 0) { | |
b.setBytes(sum.length()); | |
} | |
} | |
} | |
/** | |
* Example: | |
* | |
* <pre> | |
* public static void stringBuilder (Benchmark b) { | |
* for (int n =0; n!= b.N; ++n) { | |
* StringBuilder sum = new StringBuilder(); | |
* for (int i = 1; i!= 1000; ++i) { | |
* sum.append(i); | |
* } | |
* if (n == 0) { | |
* b.setBytes(sum.length()); | |
* } | |
* } | |
* } | |
* </pre> | |
*/ | |
public static void stringBuilder (Benchmark b) { | |
for (int n =0; n!= b.N; ++n) { | |
StringBuilder sum = new StringBuilder(); | |
for (int i = 1; i!= 1000; ++i) { | |
sum.append(i); | |
} | |
if (n == 0) { | |
b.setBytes(sum.length()); | |
} | |
} | |
} | |
/** | |
* Example: | |
* | |
* <pre> | |
* | |
* public static void string (Benchmark b) { | |
* for (int n =0; n!= b.N; ++n) { | |
* String sum = ""; | |
* for (int i = 1; i!= 1000; ++i) { | |
* sum += i; | |
* } | |
* if (n == 0) { | |
* b.setBytes(sum.getBytes().length); | |
* } | |
* } | |
* } | |
* </pre> | |
*/ | |
public static void string (Benchmark b) { | |
for (int n =0; n!= b.N; ++n) { | |
String sum = ""; | |
for (int i = 1; i!= 1000; ++i) { | |
sum += i; | |
} | |
if (n == 0) { | |
b.setBytes(sum.getBytes().length); | |
} | |
} | |
} | |
static void usage() { | |
System.err.println("usage: [jre] Benchmark <name of benchmark class> [optional regexp]"); | |
System.exit(1); | |
} | |
public static void main (String [] args) throws Throwable { | |
String regexp = null; | |
if (args.length != 1 && args.length != 2) { | |
usage(); | |
} | |
if (args.length == 2) { | |
regexp = args[1]; | |
} | |
Class c = Class.forName(args[0]); | |
runBenchmark(c, regexp); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment