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 を含む数学ライブラリを書くのでしょうか? 不変は「空想」だけで役に立たないのですか?

不変クラスはハッシュマップキーとして安全であるか、競合状態を持たないことを知っていますが、それはどこでも不変性なしで処理できる特殊なケースです。

編集: 1つの回答で示唆されているように、このマイクロベンチマークをキャリパーで再実行しましたが、15倍ではなく12倍遅く、同じポイントで実行されます。Caliper ベンチマーク用に変更されたコード:

com.google.caliper.Runner をインポートします。
com.google.caliper.SimpleBenchmark をインポートします。



最終クラス MutableInt {
    プライベート int 値;

    public int getValue() {
        戻り値;
    }

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

    public MutableInt() {
        これ(0);
    }

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

最終クラス ImmutableInt {
    プライベートの最終的な int 値。

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

    public int getValue() {
        戻り値;
    }
}


public class TestImmutableSpeed extends SimpleBenchmark {

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

キャリパーの出力:

0% シナリオ{vm=java、トライアル=0、ベンチマーク=可変、タイプ=-サーバー、minMemory=-Xms256m、最適化=-XX:+AggressiveOpts} 91614044.60 ns; ?=250338.20 ns @ 3 回の試行
50% シナリオ{vm=java、トライアル=0、ベンチマーク=不変、タイプ=-サーバー、minMemory=-Xms256m、最適化=-XX:+AggressiveOpts} 1108057922.00 ns; ?=3920760.98 ns @ 3 回の試行

ベンチマーク ミリ秒線形ランタイム
  ミュータブル 91.6 ==
不変 1108.1 ==============================

Caliper の JVM 出力の最適化パラメータがないことに注意してください。

0% シナリオ{vm=java、トライアル=0、ベンチマーク=可変} 516562214.00 ns; ?=623120.57 ns @ 3 回の試行
50% シナリオ{vm=java、トライアル=0、ベンチマーク=不変} 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

文字列更新

intそれで、私はこれについてもっと考えていたので、ラップされたクラスを からオブジェクト、この場合は に変更してみることにしましたString。静的クラスをStrings に変更し、文字列を でロードし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

ただし、この場合の違いは、sを使用していたという事実ではなく、発生する必要があるすべての配列のコピーによって支配されると思いますString

于 2013-05-01T11:47:10.557 に答える
4

不変値により、Java クリーナーでのクリーンなプログラミングが可能になります。離れた場所で不気味なアクションが発生するのを避けるために、どこにでもコピーする必要はありません (つまり、ある場所で値を変更すると、別の場所の値が変更されることを意味します)。コピーを削除すると速度が向上する場所もありますが、新しいインスタンスを作成すると他の領域で速度が低下します。

(C++ は、反対のアプローチをとるという点で興味深いものです。コードを書かなくても、明確に定義されたポイントでコピーを取得できます。実際、コピーを削除するにはコードを書かなければなりません。)

パフォーマンスに関心がある場合は、可変複合体も良くありません。たとえば、実装に隠されている単一の double 配列を使用する複雑な配列クラス、または生の double 配列のみを使用する方がはるかに優れています。

90 年代に、Guy Steele は、言語自体を完成させる一環として Java に値型を追加するというアイデアについて言及しました。これは非常に限定的な提案でしたが、同様の構造体 C# が後に導入されましたが、どちらもおそらく Java で最も明白な値クラスである文字列には対応していません。

于 2013-05-01T11:46:58.920 に答える
1

不変性には、速度の低下が伴う場合があります。速度が重要な場合は、変更可能な Complex で数学ライブラリを使用します。

于 2013-05-01T13:34:38.807 に答える