I'm trying to write a method like this:
static boolean fitsInDouble(long x) {
// return true if x can be represented
// as a numerically-equivalent double
}
And I'm trying to find the most efficient implementation. I settled on one, but then a coworker ran the benchmarks and got different relative results. The fastest implementation for me isn't the fastest for him.
Is there something wrong with these benchmarks?
package rnd;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(1)
@Measurement(iterations = 5)
@Warmup(iterations = 5)
public class Benchmarks {
public static void main(String[] args) throws Exception {
Options options = new OptionsBuilder()
.include(Benchmarks.class.getName())
.build();
new Runner(options).run();
}
@Benchmark
public void bigDecimal(Blackhole bh) {
for (long x : NUMBERS) bh.consume(bigDecimal(x));
}
@Benchmark
public void cast(Blackhole bh) {
for (long x : NUMBERS) bh.consume(cast(x));
}
@Benchmark
public void zeros(Blackhole bh) {
for (long x : NUMBERS) bh.consume(zeros(x));
}
public static boolean bigDecimal(long x) {
BigDecimal a = new BigDecimal(x);
BigDecimal b = new BigDecimal((double) x);
return a.compareTo(b) == 0;
}
public static boolean cast(long x) {
return x == (long) (double) x
&& x != Long.MAX_VALUE;
}
public static boolean zeros(long x) {
long a = Math.abs(x);
int z = Long.numberOfLeadingZeros(a);
return z > 10 || Long.numberOfTrailingZeros(a) > 10 - z;
}
private static final long[] NUMBERS = {
0,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
-1, -2, -3, -4, -5, -6, -7, -8, -9, -10,
123, 456, 789,
-123, -456, -789,
101112, 131415, 161718,
-101112, -131415, -161718,
11L,
222L,
3333L,
44444L,
555555L,
6666666L,
77777777L,
888888888L,
9999999999L,
1111L,
22222L,
333333L,
4444444L,
55555555L,
666666666L,
7777777777L,
88888888888L,
999999999999L,
11111111,
222222222,
3333333333L,
44444444444L,
555555555555L,
6666666666666L,
77777777777777L,
888888888888888L,
9999999999999999L,
Long.MAX_VALUE,
Long.MAX_VALUE - 1,
Long.MIN_VALUE,
Long.MIN_VALUE + 1,
(1L << 53),
(1l << 53) + 1,
(1l << 53) + 2,
(1l << 60),
(1l << 60) + 1,
(1l << 60) + 8,
(1l << 60) + 32,
(1l << 60) + 64,
(1l << 60) + 128,
(1l << 60) + 256,
(-1L << 53),
(-1L << 53) - 1,
(-1L << 53) - 2,
(-1l << 60),
(-1l << 60) - 1,
(-1l << 60) - 8,
(-1l << 60) - 32,
(-1l << 60) - 64,
(-1l << 60) - 128,
(-1l << 60) - 256
};
}
There are small differences in our environments.
Me: Windows 10, JDK 1.8.0_45, "zeros" is the fastest
Him: Windows 7, JDK 1.8.0_20, "cast" is the fastest
Our results are self-consistent from run to run, whether running in an IDE or from the command line. We're using JMH 1.10.5.
What is happening here? The benchmark seems untrustworthy and I don't know how to fix it.
cast
test, though the difference is not very much (like 380 ns vs 390 ns). For further investigation please provide full per-iteration log for both machines. – Tuff