3

免責事項:私はこの質問この質問に目を通しまし たが、どちらも細部と一般的な最適化は不要な懸念によって脱線しました。現在のアプリで得られるすべてのパフォーマンスが本当に必要です。このアプリは、リアルタイムで MIDI データを受信し、処理し、吐き出しています。また、可能な限りスケールアップする必要があります 。

array小さなリストの多数の読み取りでのパフォーマンスとArrayList、変数を手に持っているだけのパフォーマンスを比較しています。私は、配列ArrayListが 2.5 倍も優れており、オブジェクト参照だけを上回っていることを発見しました。

私が知りたいのは:

  1. 私のベンチマークは大丈夫ですか?テストの順序と実行回数を変更せずに入れ替えました。また、ナノ秒の代わりにミリ秒を使用しても無駄になりました。
  2. この違いを最小限に抑えるために、Java オプションを指定する必要がありますか?
  3. この違いが本当なら、この場合、この状況でそれらを変換するために必要なコードを入れるべきではないでしょTest[]うか? ArrayList<Test>明らかに、私は書くことよりも多くのことを読んでいます。

JVM は OSX 上の Java 1.6.0_17 であり、間違いなく Hotspot モードで実行されています。

  public class ArraysVsLists {

    static int RUNS = 100000;

    public static void main(String[] args) {
        long t1;
        long t2;

        Test test1 = new Test();
        test1.thing = (int)Math.round(100*Math.random());
        Test test2 = new Test();
        test2.thing = (int)Math.round(100*Math.random());

        t1 = System.nanoTime();

        for (int i=0; i<RUNS; i++) {
            test1.changeThing(i);
            test2.changeThing(i);
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1) + " How long NO collection");

        ArrayList<Test> list = new ArrayList<Test>(1);
        list.add(test1);
        list.add(test2);
        // tried this too: helps a tiny tiny bit 
        list.trimToSize();

        t1= System.nanoTime();

        for (int i=0; i<RUNS; i++) {
            for (Test eachTest : list) {
                eachTest.changeThing(i);
            }
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1) + " How long collection");


        Test[] array = new Test[2];
        list.toArray(array);

        t1= System.nanoTime();

        for (int i=0; i<RUNS; i++) {
            for (Test test : array) {
                test.changeThing(i);
            }
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1) + " How long array ");

    }
}

class Test {
    int thing;
    int thing2;
    public void changeThing(int addThis) {
        thing2 = addThis + thing;
    }
}
4

3 に答える 3

1

ベンチマークは、実際のユースケースがベンチマークコードと一致する場合、つまり各要素に対する操作が非常に少ない場合にのみ有効です。そのため、実行時間は、操作自体ではなく、アクセス時間によって大きく左右されます。その場合はそうです。パフォーマンスが重要な場合は、アレイを使用する必要があります。ただし、実際のユースケースで要素ごとの実際の計算がはるかに多い場合、要素ごとのアクセス時間はそれほど重要ではなくなります。

于 2010-02-09T09:33:24.313 に答える
1

マイクロベンチマークは、Java のようなプラットフォームで正しく取得するのは非常に困難です。ベンチマークするコードを別のメソッドに抽出し、ウォームアップとして数千回実行してから測定する必要があります。私はそれを行いました(以下のコード)。その結果、参照を介した直接アクセスは配列を介した場合の 3 倍高速ですが、コレクションは依然として 2 倍遅くなります。

これらの数値は、JVM オプションに基づいています-server -XX:+DoEscapeAnalysis。を使用しない-serverと、コレクションの使用が大幅に遅くなります (ただし、不思議なことに、直接および配列アクセスはかなり高速であり、奇妙なことが起こっていることを示しています)。-XX:+DoEscapeAnalysisコレクションはさらに 30% 高速化されますが、実際の運用コードでも同様に機能するかどうかは非常に疑問です。

全体的な私の結論は次のようになります: マイクロベンチマークは忘れてください。アプリケーション全体を書き直すことなく、できる限り本番コードに近い測定を行います。

import java.util.ArrayList;

public class ArrayTest {

    static int RUNS_INNER = 1000;
    static int RUNS_WARMUP = 10000;
    static int RUNS_OUTER = 100000;

    public static void main(String[] args) {
        long t1;
        long t2;

        Test test1 = new Test();
        test1.thing = (int)Math.round(100*Math.random());
        Test test2 = new Test();
        test2.thing = (int)Math.round(100*Math.random());

        for(int i=0; i<RUNS_WARMUP; i++)
        {
            testRefs(test1, test2);            
        }
        t1 = System.nanoTime();
        for(int i=0; i<RUNS_OUTER; i++)
        {
            testRefs(test1, test2);            
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1)/1000000.0 + " How long NO collection");

        ArrayList<Test> list = new ArrayList<Test>(1);
        list.add(test1);
        list.add(test2);
        // tried this too: helps a tiny tiny bit 
        list.trimToSize();

        for(int i=0; i<RUNS_WARMUP; i++)
        {
            testColl(list);
        }
        t1= System.nanoTime();

        for(int i=0; i<RUNS_OUTER; i++)
        {
            testColl(list);
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1)/1000000.0 + " How long collection");


        Test[] array = new Test[2];
        list.toArray(array);

        for(int i=0; i<RUNS_WARMUP; i++)
        {
            testArr(array);            
        }
        t1= System.nanoTime();

        for(int i=0; i<RUNS_OUTER; i++)
        {
            testArr(array);
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1)/1000000.0 + " How long array ");

    }

    private static void testArr(Test[] array)
    {
        for (int i=0; i<RUNS_INNER; i++) {
            for (Test test : array) {
                test.changeThing(i);
            }
        }
    }

    private static void testColl(ArrayList<Test> list)
    {
        for (int i=0; i<RUNS_INNER; i++) {
            for (Test eachTest : list) {
                eachTest.changeThing(i);
            }
        }
    }

    private static void testRefs(Test test1, Test test2)
    {
        for (int i=0; i<RUNS_INNER; i++) {
            test1.changeThing(i);
            test2.changeThing(i);
        }
    }
}

class Test {
    int thing;
    int thing2;
    public void changeThing(int addThis) {
        thing2 = addThis + thing;
    }
}
于 2010-02-09T10:29:29.823 に答える
0

おそらく有効ではありません。JIT コンパイラの仕組みを理解していれば、メソッドをコンパイルしても、既に実行中のメソッドの呼び出しには影響しません。メソッドは 1 回しか呼び出されないため、main最終的には解釈されます。ほとんどの作業はそのメソッドの本体で行われるため、得られる数値は通常の実行を特に示すものではありません。

JIT コンパイルの影響は、コレクションがない場合が配列の場合よりも遅い理由を説明するのに役立つかもしれません。その結果は直感に反しており、あなたが報告した他のベンチマーク結果に疑問を投げかけています.

于 2010-02-09T10:03:50.150 に答える