Java immutable classes are much slower
I need some complex math libraries, so I hesitate to use immutable complex libraries and variable complex libraries Obviously, I want the computation to run fairly fast (unless it kills readability, etc.)
So I created a simple test speed variable vs immutable:
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"); } }
If the test runs too slowly / fast, you can resize the array
I run: - server - xms256m - XX: aggregateopts I get:
sum = 2199023247360 sum = 2199023247360 speed comparison mutable vs immutable: mutable 102 ms immutable 1506 ms
Question: do I lack some optimization parameters, or is the immutable version 15x slow?
If so, why do people write math libraries with immutable class complexity? Is it immutable, just "fancy" but useless?
I know that immutable classes are more secure or have no competition conditions for hash mapping keys, but these special cases can be handled unchanged anywhere
Editor: according to an answer suggestion, I rerun this micro benchmark with calipers. It runs 12 times faster than 15 times Change caliper benchmark Code:
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
Caliper output:
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 ==============================
Note that the optimization parameters for JVM output without caliper are:
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 ==============================
So bad parameters make both versions slow, but the ratio is not terrible (but not important)
Solution
It's fascinating, so first of all, it's not a fair test; When you do this, you won't heat the JVM Benchmarking is often difficult to do I refactor your code using Google caliper and get similar but different results; Invariant classes are only three times faster Do not know why? In any case, the work here so far:
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]); } }
Caliper output
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
String update
So I was thinking about this more, and I decided to try to change the wrapped class from an int to an object, in this case a string Change the static class to a string and use integer valueOf(i). Tostring() loads strings instead of adding them, and attaches them to StringBuilder. I get the following results:
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
However, I think in this case, the difference is mainly the fact that all array replication must occur, not the use of strings