首页 文章

java不可变类慢得多

提问于
浏览
10

我需要一些复杂的数学库,所以我在使用不可变复杂的库和使用可变复杂的库之间犹豫不决 . 显然,我希望计算运行得相当快(除非它会杀死可读性等) .

所以我创建了速度可变与不可变的简单测试:

final class MutableInt {
    private int value;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public MutableInt() {
        this(0);
    }

    public MutableInt(int value) {
        this.value = value;
    }   
}

final class ImmutableInt {
    private final int value;

    public ImmutableInt(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

public class TestImmutableSpeed {

    static long testMutable(final int arrLen) {
        MutableInt[] arrMutable = new MutableInt[arrLen];
        for (int i = 0; i < arrMutable.length; ++i) {
            arrMutable[i] = new MutableInt(i);
            for (int j = 0; j < arrMutable.length; ++j) {
                arrMutable[i].setValue(arrMutable[i].getValue() + j);
            }
        }
        long sumMutable = 0;
        for (MutableInt item : arrMutable) {
            sumMutable += item.getValue();
        }
        return sumMutable;
    }

    static long testImmutable(final int arrLen) {
        ImmutableInt[] arrImmutable = new ImmutableInt[arrLen];
        for (int i = 0; i < arrImmutable.length; ++i) {
            arrImmutable[i] = new ImmutableInt(i);
            for (int j = 0; j < arrImmutable.length; ++j) {
                arrImmutable[i] = new ImmutableInt(arrImmutable[i].getValue() + j);
            }
        }
        long sumImmutable = 0;
        for (ImmutableInt item : arrImmutable) {
            sumImmutable += item.getValue();
        }
        return sumImmutable;
    }

    public static void main(String[] args) {
        final int arrLen = 1<<14;

        long tmStart = System.nanoTime();
        System.out.println("sum = " + testMutable(arrLen));
        long tmMid = System.nanoTime();
        System.out.println("sum = " + testImmutable(arrLen));
        long tmEnd = System.nanoTime();

        System.out.println("speed comparison mutable vs immutable:");
        System.out.println("mutable   " + (tmMid - tmStart)/1000000 + " ms");
        System.out.println("immutable " + (tmEnd - tmMid)/1000000 + " ms");
    }
}

如果测试运行太慢/太快,您可以调整数组的大小 .

我运行:-server -Xms256m -XX:AggressiveOpts我得到:

sum = 2199023247360
sum = 2199023247360
speed comparison mutable vs immutable:
mutable   102 ms
immutable 1506 ms

问题:我是否缺少一些优化参数,或者是不可变版本15x较慢?

如果是的话,为什么有人会在其中编写带有不可变类Complex的数学库?不可变只是“花哨”而无用吗?

我知道不可变类比哈希映射键更安全,或者不具备竞争条件,但这是特殊情况,可以在没有不可变性的情况下处理 .

Edit: 我使用卡尺重新运行这个微基准测试,如一个答案所示,它运行速度慢12倍,而不是15倍,仍然是相同点 . 更改了Caliper基准测试的代码:

import com.google.caliper.Runner;
import com.google.caliper.SimpleBenchmark;



final class MutableInt {
    private int value;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public MutableInt() {
        this(0);
    }

    public MutableInt(int value) {
        this.value = value;
    }   
}

final class ImmutableInt {
    private final int value;

    public ImmutableInt(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}


public class TestImmutableSpeed extends SimpleBenchmark {

    static long testMutable(final int arrLen) {
        MutableInt[] arrMutable = new MutableInt[arrLen];
        for (int i = 0; i

卡尺输出:

0% Scenario{vm=java, trial=0, benchmark=Mutable, type=-server, minMemory=-Xms256m, optimizations=-XX:+AggressiveOpts} 91614044.60 ns; ?=250338.20 ns @ 3 trials
50% Scenario{vm=java, trial=0, benchmark=Immutable, type=-server, minMemory=-Xms256m, optimizations=-XX:+AggressiveOpts} 1108057922.00 ns; ?=3920760.98 ns @ 3 trials

benchmark     ms linear runtime
  Mutable   91.6 ==
Immutable 1108.1 ==============================

请注意,没有Caliper的JVM输出的优化参数是:

0% Scenario{vm=java, trial=0, benchmark=Mutable} 516562214.00 ns; ?=623120.57 ns @ 3 trials
50% Scenario{vm=java, trial=0, benchmark=Immutable} 1706758503.00 ns; ?=5842389.60 ns @ 3 trials

benchmark   ms linear runtime
  Mutable  517 =========
Immutable 1707 ==============================

如此糟糕的参数使两个版本都变慢,但比例不那么糟糕(但仍然不重要) .

3 回答

  • 6

    这很有趣 . 嗯,首先,这不是一个公平的考验;当你这样做的时候,你没有为JVM做准备 . 基准测试通常很难做到 . 我重构了你的代码以使用Google Caliper,并获得了类似但不同的结果;不可变类只慢了3倍 . 不知道为什么 . 无论如何这里是迄今为止的工作:

    TestImmutableSpeed.java

    import com.google.caliper.Runner;
    import com.google.caliper.SimpleBenchmark;
    
    public class TestImmutableSpeed {
        static final class MutableInt {
            private int value;
    
            public int getValue() {
                return value;
            }
    
            public void setValue(int value) {
                this.value = value;
            }
    
            public MutableInt() {
                this(0);
            }
    
            public MutableInt(int value) {
                this.value = value;
            }   
        }
    
        static final class ImmutableInt {
            private final int value;
    
            public ImmutableInt(int value) {
                this.value = value;
            }
    
            public int getValue() {
                return value;
            }
        }
    
        public static class TestBenchmark extends SimpleBenchmark {
            public void timeMutable(final int arrLen) {
                MutableInt[] arrMutable = new MutableInt[arrLen];
                for (int i = 0; i < arrMutable.length; ++i) {
                    arrMutable[i] = new MutableInt(i);
                    for (int j = 0; j < arrMutable.length; ++j) {
                        arrMutable[i].setValue(arrMutable[i].getValue() + j);
                    }
                }
                long sumMutable = 0;
                for (MutableInt item : arrMutable) {
                    sumMutable += item.getValue();
                }
                System.out.println(sumMutable);
            }
    
            public void timeImmutable(final int arrLen) {
                ImmutableInt[] arrImmutable = new ImmutableInt[arrLen];
                for (int i = 0; i < arrImmutable.length; ++i) {
                    arrImmutable[i] = new ImmutableInt(i);
                    for (int j = 0; j < arrImmutable.length; ++j) {
                        arrImmutable[i] = new ImmutableInt(arrImmutable[i].getValue() + j);
                    }
                }
                long sumImmutable = 0;
                for (ImmutableInt item : arrImmutable) {
                    sumImmutable += item.getValue();
                }
                System.out.println(sumImmutable);
            }
        }
    
        public static void main(String[] args) {
            Runner.main(TestBenchmark.class, new String[0]);
        }
    }
    

    卡尺输出

    0% Scenario{vm=java, trial=0, benchmark=Immutable} 78574.05 ns; σ=21336.61 ns @ 10 trials
     50% Scenario{vm=java, trial=0, benchmark=Mutable} 24956.94 ns; σ=7267.78 ns @ 10 trials
    
     benchmark   us linear runtime
     Immutable 78.6 ==============================
       Mutable 25.0 =========
    
     vm: java
     trial: 0
    

    字符串更新

    所以我更多地考虑这个问题,我决定尝试将包装类从 int 更改为对象,在本例中为 String . 将静态类更改为 String ,并使用 Integer.valueOf(i).toString() 加载字符串,而不是添加,将它们附加到 StringBuilder 中,我得到了以下结果:

    0% Scenario{vm=java, trial=0, benchmark=Immutable} 11034616.91 ns; σ=7006742.43 ns @ 10 trials
    50% Scenario{vm=java, trial=0, benchmark=Mutable} 9494963.68 ns; σ=6201410.87 ns @ 10 trials
    
    benchmark    ms linear runtime
    Immutable 11.03 ==============================
      Mutable  9.49 =========================
    
    vm: java
    trial: 0
    

    但是,我认为在这种情况下,差异主要是所有必须发生的阵列复制,而不是它使用 String 的事实 .

  • 3

    不可变性有时会带来速度惩罚 . 如果速度很重要,请使用带有可变复合体的数学库 .

  • 1

    不可变的值使得Java中的干净编程更加清晰 . 你无需在任何地方进行复制,以避免在远处结束幽灵般的动作(我的意思是在一个地方改变一个值而无意中改变另一个地方的值) . 删除副本会加速某些地方的速度,但创建新实例会减慢其他区域的速度 .

    (C很有意思,它采用了相反的方法 . 你可以在定义良好的点上获得副本,而无需编写任何代码 . 实际上,你必须编写代码才能删除复制 . )

    如果你关心的是性能,那么可变复合体也不好 . 比如,一个复杂的数组类使用隐藏在实现中的单个双数组,或者只是双数组raw更好 .

    回到九十年代,Guy Steele提到了向Java添加值类型的想法,作为使语言本身完整的一部分 . 尽管这是一个非常有限的提议,但后来引入了类似的结构C#,但它们都没有说明Java中最明显的值类,即字符串 .

相关问题