java不可变类慢得多
我需要一些复杂的数学库,所以我犹豫在使用不可变复杂的库和使用可变复杂的库之间。 显然,我希望计算能够合理快速地运行(除非它杀死可读性等)。
所以我创建了速度可变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
问题:我是否缺少一些优化参数,或者是不可变的15倍慢的版本?
如果是这样,为什么有人会写一个带有不可变类Complex的数学库? 不可变只是“幻想”而无用吗?
我知道不可变类与散列映射密钥相比更安全,或者不存在竞争条件,但这是特殊情况,可以在任何地方都无法处理的情况下处理。
编辑:我用caliper重新运行这个microbenchmark,正如一个答案所建议的那样,它运行速度慢12倍,而不是15x,仍然是相同点。 更改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 ==============================
如此糟糕的参数会使版本变慢,但比例不太可怕(但并不重要)。
这很有趣。 那么,首先,这不是一个公平的考验; 当你这样做时,你并没有对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
s,并使用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
的事实。
不变的值使得Java更干净的编程。 你不必复制到任何地方,以避免结束在远处的怪异行为(我的意思是改变一个地方的值无意中改变了另一个值)。 删除副本可以加快速度,但创建新实例会减慢其他方面的速度。
(C ++很有趣,它采用了相反的方法,你可以在没有编写任何代码的情况下获得定义好的复制点,而且你必须编写代码才能删除复制。)
如果你关心的是性能,一个可变的复合体也是不好的。 比如说,一个复杂的数组类,它使用隐藏在实现中的单个双数组,或者只是双数组raw。
早在九十年代,Guy Steele提到了为Java添加值类型的想法,这是完成语言本身的一部分。 虽然这是一个非常有限的提议,但C#后来引入了类似的结构,但它们都不能解决Java中最显而易见的值类,即字符串。
不可移动性有时伴随着速度惩罚。 如果速度很重要,请使用具有可变复杂度的数学库。
链接地址: http://www.djcxy.com/p/36367.html