10

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

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

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 我得到:

总和 = 2199023247360
总和 = 2199023247360
速度比较可变与不可变:
可变 102 毫秒
不可变 1506 毫秒

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

如果是这样,为什么有人会在其中编写包含不可变类 Complex 的数学库?不可变只是“花哨”但没用吗?

我知道不可变类作为散列映射键更安全,或者不能有竞争条件,但这是可以处理的特殊情况,没有任何地方的不变性。

编辑:我用卡尺重新运行这个微基准测试,正如一个答案所建议的那样,它的运行速度慢了 12 倍,而不是 15 倍,仍然是同一点。更改了 Caliper 基准测试的代码:

导入 com.google.caliper.Runner;
导入 com.google.caliper.SimpleBenchmark;



最后一类 MutableInt {
    私有 int 值;

    公共 int getValue() {
        返回值;
    }

    公共无效setValue(int值){
        this.value = 值;
    }

    公共 MutableInt() {
        这(0);
    }

    公共可变整数(整数值){
        this.value = 值;
    }   
}

最终类 ImmutableInt {
    私有最终 int 值;

    公共不可变整数(整数值){
        this.value = 值;
    }

    公共 int getValue() {
        返回值;
    }
}


公共类 TestImmutableSpeed 扩展 SimpleBenchmark {

    静态长 testMutable(final int arrLen) {
        MutableInt[] arrMutable = new MutableInt[arrLen];
        对于 (int i = 0; 我

卡尺输出:

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

基准毫秒线性运行时间
  可变 91.6 ==
不可变 1108.1 ===============================

请注意,如果没有 Caliper 的 JVM 输出的优化参数,则为:

0% 场景{vm=java, trial=0, benchmark=Mutable} 516562214.00 ns; ?=623120.57 ns @ 3 次试验
50% 场景{vm=java, trial=0, benchmark=Immutable} 1706758503.00 ns; ?=5842389.60 ns @ 3 次试验

基准毫秒线性运行时间
  可变 517 =========
不可变1707 ===============================

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

4

3 回答 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

字符串更新

所以我在考虑这个问题,我决定尝试将包装类从 anint更改为对象,在本例中为 a String。将静态类更改为Strings,并使用 加载字符串Integer.valueOf(i).toString(),而不是添加,将它们附加到 a 中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但是,我认为在这种情况下,差异主要在于必须发生的所有数组复制,而不是它使用s的事实。

于 2013-05-01T11:47:10.557 回答
4

不可变的值使 Java 中的干净编程更干净。您不必到处复制以避免在远处发生令人毛骨悚然的动作(我的意思是更改一个地方的值会无意中更改另一个地方的值)。删除副本会在某些地方加快速度,但在其他区域创建新实例会减慢速度。

(C++ 的有趣之处在于它采用了相反的方法。您可以在明确定义的点获得副本,而无需编写任何代码。事实上,您必须编写代码才能删除复制。)

如果您关心的是性能,那么可变复合体也不好。比方说,有一个复杂的数组类更好,它使用隐藏在实现中的单个双数组,或者只是双数组原始。

早在 90 年代,Guy Steele 就提到了将值类型添加到 Java 中作为使语言本身完整的一部分的想法。虽然这是一个非常有限的提议,后来引入了类似的结构 C#,但它们都不能处理可以说是 Java 中最明显的值类,即字符串。

于 2013-05-01T11:46:58.920 回答
1

不变性有时会带来速度损失。如果速度很重要,请使用具有可变 Complex 的数学库。

于 2013-05-01T13:34:38.807 回答