Java等于原始对象速度

时间:2017-04-25 14:04:28

标签: java performance equals

在使用Objects.equals vs Primitive比较时,尝试测试equals的速度。如果有人需要代码:

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();
    }
}

我最终看到的结果是:

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

你认为使用原始比较来获得更快的equals方法(可能忽略代码中的其他操作),或者使用Objects.equals来统一代码(不考虑使用Double.compare和Float.compare在equals方法中分别使用double和float原语,以及== for other primitives?

3 个答案:

答案 0 :(得分:1)

可以在字节码输出中看到两个代码之间的差异。

原始值比较只需使用一条if_icmpne指令完成,就是这样。

请参阅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                 

另一方面,对象比较(Object.equals)要求在比较发生之前将基元加到它们的Object等价物(即int到Integer,byte到Byte,char到Character等)。一旦两个基元都被装箱,就会调用额外的invokestatic指令(Objects.equals)来完成比较(在内部进行原始比较和空检查等)。

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  

答案 1 :(得分:0)

使用包装器而不是原语应始终有原因。基本上,你应该使用原语,但有时你需要一个包装器。

不同之处在于包装器可以是null,并且原语总是设置为它的初始值。这意味着,当您想要拥有自己的初始状态,或者想要知道收到的int是0还是不存在时,您肯定会使用包装器。

比较基元比比较包装更快,这并不奇怪。调用equals成本会调用任何其他方法成本。无论如何,你的测试还应该比较比较大数字时的差异。现在,我们只能说,比较原始的比比较包装的更快。

查看Integer缓存-128到127之间的数字。这会发生很大变化。

答案 2 :(得分:0)

您必须使用equals方法,因为' =='检查基本类型之间的值相等或相等的对象标识(即操作数是否是相同的实例,而不仅仅是逻辑上相等)。

这一切都评估为真:

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

然而,这种评价是错误的,与你的预期相反:

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

如果您要比较基本类型或希望实际检查引用相等性以查看两个操作数是否是同一实例,则仅使用==。即使将原始类型作为一个操作数而将包装类型作为另一个操作数也是最好的,因为如果包装变量为空,则自动拆箱可能导致nullpointer异常。可以说==也可以用于枚举常量,但这往往导致......争论。