/*
* Copyright (C) 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package tutorial;
import com.google.caliper.BeforeExperiment;
import com.google.caliper.Benchmark;
import com.google.caliper.Param;
/**
* Caliper tutorial. To run the example benchmarks in this file:
* {@code CLASSPATH=... [caliper_home]/caliper tutorial.Tutorial.Benchmark1}
*/
public class Tutorial {
/*
* We begin the Caliper tutorial with the simplest benchmark you can write.
* We'd like to know how efficient the method System.nanoTime() is.
*
* Notice:
*
* - We write a class that extends com.google.caliper.Benchmark.
* - It contains a public instance method whose name begins with 'time' and
* which accepts a single 'int reps' parameter.
* - The body of the method simply executes the code we wish to measure,
* 'reps' times.
*
* Example run:
*
* $ CLASSPATH=build/classes/test caliper tutorial.Tutorial.Benchmark1
* [real-time results appear on this line]
*
* Summary report for tutorial.Tutorial$Benchmark1:
*
* Benchmark ns
* --------- ---
* NanoTime 233
*/
public static class Benchmark1 {
@Benchmark void timeNanoTime(int reps) {
for (int i = 0; i < reps; i++) {
System.nanoTime();
}
}
}
/*
* Now let's compare two things: nanoTime() versus currentTimeMillis().
* Notice:
*
* - We simply add another method, following the same rules as the first.
*
* Example run output:
*
* Benchmark ns
* ----------------- ---
* NanoTime 248
* CurrentTimeMillis 118
*/
public static class Benchmark2 {
@Benchmark void timeNanoTime(int reps) {
for (int i = 0; i < reps; i++) {
System.nanoTime();
}
}
@Benchmark void timeCurrentTimeMillis(int reps) {
for (int i = 0; i < reps; i++) {
System.currentTimeMillis();
}
}
}
/*
* Let's try iterating over a large array. This seems simple enough, but
* there is a problem!
*/
public static class Benchmark3 {
private final int[] array = new int[1000000];
@SuppressWarnings("UnusedDeclaration") // IDEA tries to warn us!
@Benchmark void timeArrayIteration_BAD(int reps) {
for (int i = 0; i < reps; i++) {
for (int ignoreMe : array) {}
}
}
}
/*
* Caliper reported that the benchmark above ran in 4 nanoseconds.
*
* Wait, what?
*
* How can it possibly iterate over a million zeroes in 4 ns!?
*
* It is very important to sanity-check benchmark results with common sense!
* In this case, we're indeed getting a bogus result. The problem is that the
* Java Virtual Machine is too smart: it detected the fact that the loop was
* producing no actual result, so it simply compiled it right out. The method
* never looped at all. To fix this, we need to use a dummy result value.
*
* Notice:
*
* - We simply change the 'time' method from 'void' to any return type we
* wish. Then we return a value that can't be known without actually
* performing the work, and thus we defeat the runtime optimizations.
* - We're no longer timing *just* the code we want to be testing - our
* result will now be inflated by the (small) cost of addition. This is an
* unfortunate fact of life with microbenchmarking. In fact, we were
* already inflated by the cost of an int comparison, "i < reps" as it was.
*
* With this change, Caliper should report a much more realistic value, more
* on the order of an entire millisecond.
*/
public static class Benchmark4 {
private final int[] array = new int[1000000];
@Benchmark int timeArrayIteration_fixed(int reps) {
int dummy = 0;
for (int i = 0; i < reps; i++) {
for (int doNotIgnoreMe : array) {
dummy += doNotIgnoreMe;
}
}
return dummy; // framework ignores this, but it has served its purpose!
}
}
/*
* Now we'd like to know how various other *sizes* of arrays perform. We
* don't want to have to cut and paste the whole benchmark just to provide a
* different size. What we need is a parameter!
*
* When you run this benchmark the same way you ran the previous ones, you'll
* now get an error: "No values provided for benchmark parameter 'size'".
* You can provide the value requested at the command line like this:
*
* [caliper_home]/caliper tutorial.Tutorial.Benchmark5 -Dsize=100}
*
* You'll see output like this:
*
* Benchmark size ns
* -------------- ---- ---
* ArrayIteration 100 51
*
* Now that we've parameterized our benchmark, things are starting to get fun.
* Try passing '-Dsize=10,100,1000' and see what happens!
*
* Benchmark size ns
* -------------- ---- -----------------------------------
* ArrayIteration 10 7 |
* ArrayIteration 100 49 ||||
* ArrayIteration 1000 477 ||||||||||||||||||||||||||||||
*
*/
public static class Benchmark5 {
@Param int size; // set automatically by framework
private int[] array; // set by us, in setUp()
@BeforeExperiment void setUp() {
// @Param values are guaranteed to have been injected by now
array = new int[size];
}
@Benchmark int timeArrayIteration(int reps) {
int dummy = 0;
for (int i = 0; i < reps; i++) {
for (int doNotIgnoreMe : array) {
dummy += doNotIgnoreMe;
}
}
return dummy;
}
}
}