问题
Just tried to test speed of equals when using Objects.equals vs Primitive comparison. If somebody needs the code:
import org.junit.Test;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.TimeValue;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
class BaseEquals {
byte bytePrim;
short shortPrim;
int intPrim;
long longPrim;
float floatPrim;
double doublePrim;
boolean booleanPrim;
char charPrim;
BaseEquals() {
bytePrim = 1;
shortPrim = 1;
intPrim = 1;
longPrim = 1;
floatPrim = 1.0f;
doublePrim = 1.0d;
booleanPrim = true;
charPrim = '1';
}
}
class EqualsObjects extends BaseEquals {
@Override
public int hashCode() {
return Objects.hash(bytePrim,
shortPrim,
intPrim,
longPrim,
floatPrim,
doublePrim,
booleanPrim,
charPrim);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof EqualsObjects)) {
return false;
}
EqualsObjects eo = (EqualsObjects)obj;
return Objects.equals(bytePrim, eo.bytePrim)
&& Objects.equals(shortPrim, eo.shortPrim)
&& Objects.equals(intPrim, eo.intPrim)
&& Objects.equals(longPrim, eo.longPrim)
&& Objects.equals(floatPrim, eo.floatPrim)
&& Objects.equals(doublePrim, eo.doublePrim)
&& Objects.equals(booleanPrim, eo.booleanPrim)
&& Objects.equals(charPrim, eo.charPrim);
}
}
class EqualsPrimitives extends BaseEquals {
@Override
public int hashCode() {
return Objects.hash(bytePrim,
shortPrim,
intPrim,
longPrim,
floatPrim,
doublePrim,
booleanPrim,
charPrim);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof EqualsPrimitives)) {
return false;
}
EqualsPrimitives eo = (EqualsPrimitives)obj;
return bytePrim == eo.bytePrim
&& shortPrim == eo.shortPrim
&& intPrim == eo.intPrim
&& longPrim == eo.longPrim
&& Float.compare(floatPrim, eo.floatPrim) == 0
&& Double.compare(doublePrim, eo.doublePrim) == 0
&& booleanPrim == eo.booleanPrim
&& charPrim == eo.charPrim;
}
}
public class EqualsTests {
@State(Scope.Benchmark)
public static class MyState {
EqualsObjects eo1;
EqualsObjects eo2;
EqualsPrimitives ep1;
EqualsPrimitives ep2;
@Setup
public void setup() throws Throwable {
eo1 = new EqualsObjects();
eo2 = new EqualsObjects();
ep1 = new EqualsPrimitives();
ep2 = new EqualsPrimitives();
}
}
@Benchmark
public void equalsObject(MyState state) throws Throwable {
boolean b1 = state.eo1.equals(state.eo2);
boolean b2 = state.eo2.equals(state.eo1);
}
@Benchmark
public void equalsPrimitive(MyState state) throws Throwable {
boolean b1 = state.ep1.equals(state.ep2);
boolean b2 = state.ep2.equals(state.ep1);
}
@Test
public void launch() throws RunnerException {
Options options = new OptionsBuilder()
.include(this.getClass().getName() + ".*")
.mode(Mode.AverageTime)
.timeUnit(TimeUnit.MICROSECONDS)
.warmupTime(TimeValue.seconds(1))
.warmupIterations(5)
.measurementTime(TimeValue.seconds(5))
.measurementIterations(10)
.threads(2)
.forks(1)
.shouldFailOnError(true)
.shouldDoGC(true)
.build();
new Runner(options).run();
}
}
What I saw in the end is this results:
Benchmark Mode Cnt Score Error Units
EqualsTests.equalsObject avgt 10 0.026 ± 0.001 us/op
EqualsTests.equalsPrimitive avgt 10 0.011 ± 0.001 us/op
Do you think it is worth using primitive comparison to have faster equals methods (probably neglectable to other operations in code), or using Objects.equals to have unified code (not to think about using Double.compare and Float.compare for double and float primitives respectively, and == for other primitives) in equals method?
回答1:
Difference between both codes can be seen within their bytecode outputs.
Primitive value comparison is simply done with a single if_icmpne
instruction, and thats it.
See, Instructions for bytePrim == eo.bytePrim
20: astore_2
21: aload_0
22: getfield #3 // Field bytePrim:B
25: aload_2
26: getfield #3 // Field bytePrim:B
29: if_icmpne 246
On the otherhand, Object comparision (Object.equals
) requires primitives to be boxed to their Object equivalents (i.e. int to Integer, byte to Byte, char to Character etc.) before the comparison happens. Once both primitives are boxed, additional invokestatic
instruction (Objects.equals) is invoked for completing the comparison (which internally does the primitive comparison with null checking etc.)
Instructions for Objects.equals(bytePrim, eo.bytePrim)
21: aload_0
22: getfield #3 // Field bytePrim:B
25: invokestatic #4 // Method java/lang/Byte.valueOf:(B)Ljava/lang/Byte;
28: aload_2
29: getfield #3 // Field bytePrim:B
32: invokestatic #4 // Method java/lang/Byte.valueOf:(B)Ljava/lang/Byte;
35: invokestatic #30 // Method java/util/Objects.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z
38: ifeq
回答2:
Using wrappers instead of primitives should always have a reason. Basically, you should use primitive, but sometimes you need a wrapper.
The difference is that wrappers can be null
and primitive is always set to it's initial value. This means, that when you want to have your own initial states or you want to know if the received int
was 0 or wasn't present you'll definitely use wrapper.
There is no surprise that comparing primitives is faster than comparing wrappers. Calling equals
costs as calling any other method costs. Anyway, your test should also compare what is the difference when comparing huge numbers. Now, we can only say, that comparing primitive ones is faster than comparing wrapped ones.
Look Integer
caches numbers from -128 to 127. This changes a lot.
回答3:
You must use the equals
method, because '==' checks value equality between primitive types or equal object identity (i.e. whether the operands are the same instance, not just logically equal).
This all evaluates to true:
42 == 42 // primitive values
int i = 42, j = 42; i == j // primitive values
Integer i = new Integer(42); i == 42 // auto-unboxing
Integer i = 42, j = 42; i == j // cached interned Integer instance
Yet this evaluates to false, contrary to what you might expect:
Integer i = new Integer(42); Integer j = new Integer(42); i == j // not cached, different objects
Integer i = new Integer("42"); Integer j = new Integer("42"); i == j
Only use ==
if you're comparing primitive types or wish to actually check reference equality to see if both operands are the same instance. Even for a primitive type as one operand and a wrapper type as the other it's best not to, because the auto-unboxing can lead to nullpointer exceptions if the wrapper variable is null. It can be argued that ==
is also okay to use for enum constants, but that tends to lead to... debates.
来源:https://stackoverflow.com/questions/43612938/java-equals-primitive-vs-object-speed