我需要一些复杂的数学库,所以我犹豫使用不可变的复杂的库和使用可变复杂的库.显然,我希望计算运行相当快(除非它杀死可读性等等).
所以我创建了简单测试速度可变对vs不可变:
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慢?
如果是这样,为什么有人用不可变的类复杂写数学库?是不可变的只是“花哨”但没用吗?
我知道不可变类是哈希映射密钥更安全,或者不具有竞争条件,但是这些特殊情况可以在任何地方都可以无变化地处理.
编辑:我按照一个答案建议,用卡钳重新运行这个微型基准测试,它运行速度是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,benchmark=Immutable,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,benchmark=Mutable} 516562214.00 ns; ?=623120.57 ns @ 3 trials
50% Scenario{vm=java,benchmark=Immutable} 1706758503.00 ns; ?=5842389.60 ns @ 3 trials
benchmark ms linear runtime
Mutable 517 =========
Immutable 1707 ==============================
所以糟糕的参数使得这两个版本都比较慢,但比例不太可怕(但并不重要).
解决方法
这很迷人那么首先,这不是一个公平的考验;当你这样做的时候,你不会加热JVM.基准测试通常很难做到.我重构你的代码使用
Google Caliper,并得到了类似但不同的结果;不变的类只有三倍的速度.不知道为什么.无论如何这里的工作到目前为止:
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,benchmark=Immutable} 78574.05 ns; σ=21336.61 ns @ 10 trials
50% Scenario{vm=java,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.将静态类更改为字符串,并使用Integer.valueOf(i).toString()加载字符串,而不是添加,将它们附加到StringBuilder中,我得到以下结果:
0% Scenario{vm=java,benchmark=Immutable} 11034616.91 ns; σ=7006742.43 ns @ 10 trials
50% Scenario{vm=java,benchmark=Mutable} 9494963.68 ns; σ=6201410.87 ns @ 10 trials
benchmark ms linear runtime
Immutable 11.03 ==============================
Mutable 9.49 =========================
vm: java
trial: 0
然而,我认为在这种情况下,差异主要是所有阵列复制,必须发生,而不是使用Strings的事实.