I had a discussion at work today about the cost of object creation in Java. It was about the way you are supposed to use Apache Commons HashCodeBuilder and EqualsBuilder creating a new object on every single call to hashcode() or equals():
@Override
public int hashCode(){
return new HashCodeBuilder()
.append(name)
.append(length)
.append(children)
.toHashCode();
}
While I was pretty sure that the cost of creating these short lived objects on a modern JVM was pretty negligible in most use cases, I had no numbers to prove it.
Time for a biased benchmark of the simplest kind like the following:
package org.blogspot.pbrc.benchmarks;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
public class Runner {
private static final int NUM_ELEMS = 100000;
private static final int NUM_RUNS = 100;
public static void main(String[] args) {
String[] keys = createKeys();
Integer[] numerics = createNumericValues();
// priming the jvm
for (int i = 0; i < 5; i++) {
manualEqualsTest(i, keys, numerics);
autoEqualsTest(i, keys, numerics);
}
long time = 0l;
for (int i = 0; i < NUM_RUNS; i++) {
time += manualEqualsTest(i, keys, numerics);
}
System.out.format("%s %.4fsecs\n", "Manual avg:",
(time / Double.valueOf(NUM_RUNS)) / 1000000000.0);
long autotime = 0l;
for (int i = 0; i < NUM_RUNS; i++) {
autotime += autoEqualsTest(i, keys, numerics);
}
System.out.format("%s %.4fsecs\n", "Apache commons avg:",
(autotime / Double.valueOf(NUM_RUNS)) / 1000000000.0);
}
private static long autoEqualsTest(int runNumber, String[] keys,
Integer[] numerics) {
Set<CommonsEqualsAndHashcode> autoObjs = new HashSet<CommonsEqualsAndHashcode>(
NUM_ELEMS);
long start = System.nanoTime();
for (int i = 0; i < NUM_ELEMS; i++) {
autoObjs.add(new CommonsEqualsAndHashcode(keys[i], numerics[i]));
}
return System.nanoTime() - start;
}
private static long manualEqualsTest(int runNumber, String[] keys,
Integer[] numerics) {
Set<ManualEqualsAndHashCode> valueObjs = new HashSet<ManualEqualsAndHashCode>(
NUM_ELEMS);
long start = System.nanoTime();
for (int i = 0; i < NUM_ELEMS; i++) {
valueObjs.add(new ManualEqualsAndHashCode(keys[i], numerics[i]));
}
return System.nanoTime() - start;
}
private static Integer[] createNumericValues() {
Integer[] result = new Integer[NUM_ELEMS];
Random rand = new Random();
for (int i = 0; i < NUM_ELEMS; i++) {
result[i] = rand.nextInt();
}
return result;
}
private static String[] createKeys() {
String[] result = new String[NUM_ELEMS];
RandomString rand = new RandomString(32);
for (int i = 0; i < NUM_ELEMS; i++) {
result[i] = rand.nextString();
}
return result;
}
}
I used two—otherwise identical—immutable value types: one with hand-crafted equals() and hashcode(), the other using Apache Commons HashCodeBuilder and EqualsBuilder.
package org.blogspot.pbrc.benchmarks;
public class ManualEqualsAndHashCode {
public final String val;
public final Integer numeric;
public ManualEqualsAndHashCode(String val, Integer numeric) {
this.val = val;
this.numeric = numeric;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ManualEqualsAndHashCode)) {
return false;
}
ManualEqualsAndHashCode other = (ManualEqualsAndHashCode) obj;
return other.val.equals(this.val) && other.numeric.equals(this.numeric);
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + val.hashCode();
result = 31 * result + numeric;
return result;
}
}
I got the following results on a 2 GHz Intel Core 2 Duo with 4 GB 1067 MHz DDR3 running OS X 10.8.1 (12B19) with Java 1.7.06
Manual avg: 0.4052secs Apache commons avg: 0.4508secs
In this particular scenario the overhead—when using the Apache Commons classes—amounts to about ten percent. Depending on where you're coming from, this might be a price worth paying. But, of course, this test scenario of just putting items into a collection is highly synthetic so your mileage may vary.
Now go and find more flaws in the benchmark!
No comments:
Post a Comment