java - 为什么我的空检查这么慢?

标签 java enums null

所以我的代码目前看起来像这样

    public boolean in(TransactionType... types)
    {
        if (types == null || types.length == 0)
            return false;

        for (int i = 0; i < types.length; ++i)
            if (types[i] != null && types[i] == this)
                return true;
        return false;
    }

我改成了这个

    public boolean in(TransactionType... types)
    {
        if (types == null || types.length == 0)
            return false;

        for (int i = 0; i < types.length; ++i)
            if (types[i] == this)
                return true;
        return false;
    }

(TransactionType 是一个包含大约 30 个值的枚举)

结果让我震惊。在我所有的测试中,第二个要快一个数量级。我预计可能会快 2 倍,但不是一个数量级。为什么不同?是 nullcheck 慢得多还是额外数组访问发生了一些奇怪的事情?

我的基准代码是这样的

public class App
{
    public enum TransactionType
    {
        A(1, "A", "A"),
        B(3, "B", "B"),
        C(5, "C", "C"),
        D(6, "D", "D"),
        E(7, "E", "E"),
        F(8, "F", "F"),
        G(9, "G", "G"),
        H(10, "H", "H"),
        I(11, "I", "I"),
        J(12, "J", "J"),
        K(13, "K", "K"),
        L(14, "L", "L"),
        M(15, "M", "M"),
        N(16, "N", "N"),
        O(17, "O", "O"),
        P(18, "P", "P"),
        Q(19, "Q", "Q"),
        R(20, "R", "R"),
        S(21, "S", "S"),
        T(22, "T", "T"),
        U(25, "U", "U"),
        V(26, "V", "V"),
        W(27, "W", "W"),
        X(28, "X", "X"),
        Y(29, "Y", "Y"),
        Z(30, "Z", "Z"),
        AA(31, "AA", "AA"),
        AB(32, "AB", "AB"),
        AC(33, "AC", "AC"),
        AD(35, "AD", "AD"),
        AE(36, "AE", "AE"),
        AF(37, "AF", "AF"),
        AG(38, "AG", "AG"),
        AH(39, "AH", "AH"),
        AI(40, "AI", "AI"),
        AJ(41, "AJ", "AJ"),
        AK(42, "AK", "AK"),
        AL(43, "AL", "AL"),
        AM(44, "AM", "AM"),
        AN(45, "AN", "AN"),
        AO(46, "AO", "AO"),
        AP(47, "AP", "AP");

        public final static TransactionType[] aArray =
        {
            O, Z, N, Y, AB
        };

        public final static TransactionType[] bArray =
        {
            J, P, AA, L, Q, M, K, AE, AK,
            AF, AD, AG, AH
        };

        public final static TransactionType[] cArray =
        {
            S, U, V
        };

        public final static TransactionType[] dArray =
        {
            A, B, D, G, C, E,
            T, R, I, F, H, AC,
            AI, AJ, AL, AM, AN,
            AO
        };

        private int id;
        private String abbrev;
        private String name;

        private TransactionType(int id, String abbrev, String name)
        {
            this.id = id;
            this.abbrev = abbrev;
            this.name = name;
        }

        public boolean in(TransactionType... types)
        {
            if (types == null || types.length == 0)
                return false;

            for (int i = 0; i < types.length; ++i)
                if (types[i] == this)
                    return true;
            return false;
        }

        public boolean inOld(TransactionType... types)
        {
            if (types == null || types.length == 0)
                return false;

            for (int i = 0; i < types.length; ++i)
            {
                if (types[i] != null && types[i] == this)
                    return true;
            }
            return false;
        }
    }

    public static void main(String[] args)
    {
        for (int i = 0; i < 10; ++i)
            bench2();

        for (int i = 0; i < 10; ++i)
            bench1();
    }

    private static void bench1()
    {
        final TransactionType[] values = TransactionType.values();
        long runs = 0;
        long currTime = System.currentTimeMillis();
        while (System.currentTimeMillis() - currTime < 1000)
        {
            for (TransactionType value : values)
            {
                value.inOld(TransactionType.dArray);
            }
            ++runs;
        }
        System.out.println("old " + runs);
    }

    private static void bench2()
    {
        final TransactionType[] values = TransactionType.values();
        long runs = 0;
        long currTime = System.currentTimeMillis();
        while (System.currentTimeMillis() - currTime < 1000)
        {
            for (TransactionType value : values)
            {
                value.in(TransactionType.dArray);
            }
            ++runs;
        }
        System.out.println("new " + runs);
    }
}

这里是基准测试运行的结果

new 20164901
new 20084651
new 45739657
new 45735251
new 45757756
new 45726575
new 45413016
new 45649661
new 45325360
new 45380665
old 2021652
old 2022286
old 2246888
old 2237484
old 2246172
old 2268073
old 2271554
old 2259544
old 2272642
old 2268579

这是使用 Oracle JDK 1.7.0.67

最佳答案

null 检查没有完成任何事情,我也很惊讶它会产生如此大的不同。但我相信您的评论基本上回答了您自己的问题。

@Cogman 写道:

...iterating over an array involves very little branching and is a highly local operation (meaning it is likely to take a lot of advantage of the CPUs cache). The type of branching is also highly predictable and optimized for in most modern CPUs...

如果您编译您的类并使用 javap 打印出这两个方法的反汇编字节码,您将看到:

  public boolean in(App$TransactionType...);
Code:
   0: aload_1       
   1: ifnull        9
   4: aload_1       
   5: arraylength   
   6: ifne          11
   9: iconst_0      
  10: ireturn       
  11: iconst_0      
  12: istore_2      
  13: iload_2       
  14: aload_1       
  15: arraylength   
  16: if_icmpge     34
  19: aload_1       
  20: iload_2       
  21: aaload        
  22: aload_0       
  23: if_acmpne     28
  26: iconst_1      
  27: ireturn       
  28: iinc          2, 1
  31: goto          13
  34: iconst_0      
  35: ireturn       

还有:

 public boolean inOld(App$TransactionType...);
Code:
   0: aload_1       
   1: ifnull        9
   4: aload_1       
   5: arraylength   
   6: ifne          11
   9: iconst_0      
  10: ireturn       
  11: iconst_0      
  12: istore_2      
  13: iload_2       
  14: aload_1       
  15: arraylength   
  16: if_icmpge     40
  19: aload_1       
  20: iload_2       
  21: aaload        
  22: ifnull        34
  25: aload_1       
  26: iload_2       
  27: aaload        
  28: aload_0       
  29: if_acmpne     34
  32: iconst_1      
  33: ireturn       
  34: iinc          2, 1
  37: goto          13
  40: iconst_0      
  41: ireturn       

您的新方法删除了六个操作和一个潜在的分支站点。

循环以前很紧,现在 super 紧。

我原以为 Java 会将这两种方法 JIT 到本质上相同的东西。您的时间数字另有建议。

一些随机数:

1.6.33 32b:646100 与 727173

1.6.33 64b:1667665 与 2668513

1.7.67 32b:661003 与 716417

1.7.07 64b:1663926 与 32493989

1.7.60 64b:1700574 与 32368506

1.8.20 64b:1648382 与 32222823

所有 64 位 JVM 执行这两种实现的速度都比 32 位版本快得多。

关于java - 为什么我的空检查这么慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26513492/

相关文章:

java - 马文 : Using class from imported jar

java - 编译时出错

java - 初始化复杂静态值的最佳方法

php - laravel 检索请求输入返回 null

database - 存储空数据是否占用磁盘空间?

sorting - 当我尝试自己对表格进行排序时,我不断对数字和零进行比较

java - 如何在java中将不同货币的货币表示形式转换为十进制值

java - JUnit RemoteTestRunner NoClassDefFoundError Junit4 Eclipse 3.7.2

java - 如何测试 JSONObject 是否为空或不存在

java - 如何在 Frege native 声明中声明 Java 枚举?