16

次のリストは、「Dalvik Virtual Machine Internals」と呼ばれる、2008年のGoogle I / Oトークからのものであり、オブジェクトのセットを最も効率の高いものから最も低いものの順にループする方法のリストです。

(1) for (int i = initializer; i >=0; i--) //hard to loop backwards
(2) int limit = calculate_limit(); for (int i= 0; i< limit; i++)
(3) Type[] array = get_array(); for (Type obj : array)
(4) for (int i =0; i< array.length; i++) //gets array.length everytime
(5) for (int i=0; i < this.var; i++) //has to calculate what this.var is
(6) for (int i=0; i < obj.size(); i++) //even worse calls function  each time
(7) Iterable list = get_list(); for (Type obj : list) //generic object based iterators slow!

最初の3つは同じ効率領域にあり、可能であれば7つは避けてください。これは主にバッテリーの寿命を延ばすためのアドバイスですが、JavaSEコードにも役立つ可能性があります。

私の質問は、なぜ(7)が遅いのか、なぜ(3)が良いのかということです。(3)と(7)の配列とリストの違いかもしれないと思いました。また、Danが述べたように(7)は、GCする必要のある小さな一時オブジェクトを大量に作成しますが、私は最近Javaに少し錆びていますが、誰かがその理由を説明できますか?それは彼のトークビデオの0:41:10に1分間あります。

4

6 に答える 6

7

このリストは少し古くなっているため、今日ではあまり役に立ちません。

これは、Android デバイスが遅く、リソースが非常に限られていた数年前の良い参考資料でした。また、Dalvik VM の実装には、現在利用できる多くの最適化が欠けていました。

そのようなデバイスでは、単純なガベージ コレクションに1 ~ 2 秒かかりました (比較のために、現在のほとんどのデバイスでは約 20 ミリ秒かかります)。GC 中にデバイスがフリーズしたため、開発者はメモリの消費に細心の注意を払う必要がありました。

今日はそれについてあまり心配する必要はありませんが、パフォーマンスが本当に気になる場合は、いくつかの詳細を次に示します。

(1) for (int i = initializer; i >= 0; i--) //hard to loop backwards
(2) int limit = calculate_limit(); for (int i=0; i < limit; i++)
(3) Type[] array = get_array(); for (Type obj : array)

これらは理解しやすいです。比較を行う前に変数の値を読み取らないためi >= 0、評価が高速です。i < limit整数リテラルで直接動作するため、高速です。

(3) が (2) よりも遅くなる理由がわかりません。コンパイラは (2) と同じループを生成するはずですが、現時点では Dalvik VM が正しく最適化していない可能性があります。

(4) for (int i=0; i < array.length; i++) //gets array.length everytime
(5) for (int i=0; i < this.var; i++) //has to calculate what this.var is
(6) for (int i=0; i < obj.size(); i++) //even worse calls function  each time

これらはすでにコメントで説明されています。

(7) Iterable list = get_list(); for (Type obj : list)

Iterablesメモリを割り当てたり、エラー処理を行ったり、内部で複数のメソッドを呼び出したりするため、これらはすべて、反復ごとに 1 つの関数呼び出しのみを行う (6) よりもはるかに遅くなります。

于 2012-08-02T21:26:20.143 に答える
4

私の最初の答えは満足のいくものではなく、質問を説明するのに本当に役立たなかったと感じました。私はこのサイトへのリンクを投稿し、いくつかの基本的なユースケースをカバーしていましたが、問題の核心はカバーしていませんでした。そこで、代わりに少し実践的な調査を行いました。

2 つの別々のコードを実行しました。

    // Code 1
    int i = 0;
    Integer[] array = { 1, 2, 3, 4, 5 };
    for (Integer obj : array) {
        i += obj;
    }
    System.out.println(i);

    // Code 2
    int i = 0;
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    list.add(5);
    for (Integer obj : list) {
        i += obj;
    }
    System.out.println(i);

もちろん、両方とも を出力し、両方とも配列15を使用します( s なし)。Integerint

次に、javapこれらを逆アセンブルしてバイトコードを調べました。(初期化は無視forしました。ループの前はすべてコメントアウトされています。) これらはかなり長いので、PasteBin hereに投稿しました。

ここで、コード 1 のバイトコードは実際には長くなりますが、それほど集中的ではありません。invokevirtual( を除いて) 1 回だけ使用しprintln、他の呼び出しは必要ありません。コード 1 では、反復を基本的なループに最適化しているようです。配列の長さをチェックし、変数にロードしてから に追加しiます。これは、 とまったく同じように動作するように最適化されているようfor (int i = 0; i < array.length; i++) { ... }です。

ここで、コード 2 では、バイトコードがより集中的になります。上記で必要な他のすべての呼び出しに加えて、2 つのinvokeinterface呼び出し (両方とも) を行う必要があります。Iteratorさらに、コード 2 はcheckcastジェネリックIterator(前述のように最適化されていない) であるため、呼び出す必要があります。load現在、および操作の呼び出しが少ないという事実にもかかわらずstore、これらの前述の呼び出しには、かなりの量のオーバーヘッドが伴います。

彼がビデオで言っているように、これらをたくさん行う必要がある場合は、問題が発生する可能性があります。たとえば、の開始時に実行することはActivity、おそらくそれほど大きな問題ではありません。それらの多くを作成することに注意してくださいonDraw。たとえば、特に で繰り返します。

于 2012-08-02T21:01:27.350 に答える
1

コンパイラーは (3) をこれに最適化すると思います (これは私が推測している部分です):

for (int i =0; i < array.length; ++i)
{
    Type obj = array[i];

}

また、(7) は最適化できません。これは、コンパイラが Iterable の種類を認識していないためです。つまり、実際new Iteratorにはヒープ上に を作成する必要があります。メモリの割り当てにはコストがかかります。そして、次のオブジェクトを要求するたびに、いくつかの呼び出しが行われます。

(7)がコンパイルされたときに何が起こるかの大まかなスケッチを与えるには(これについては確かです):

Iterable<Type> iterable = get_iterable();
Iterator<Type> it = iterable.iterator(); // new object on the heap
while (it.hasNext()) // method call, some pushing and popping to the stack
{
    Type obj = it.next(); // method call, again pushing and popping


}
于 2012-08-02T20:21:17.893 に答える
0

オブジェクトを「リンクされたリスト」ベースのイテレーターにマーシャリングしてから、メモリのチャンクとポインター (配列) ではなく、API をサポートする必要があると思います。

于 2012-08-02T20:17:54.660 に答える
0

Android の場合、これは 2015 年の Google Developers からのビデオです。

索引付けまたは反復するには? (Android パフォーマンス パターン シーズン 2 ep6) https://www.youtube.com/watch?v=MZOf3pOAM6A

彼らは DALVIK ランタイム、4.4.4 ビルドで 10 回テストを行い、平均的な結果を得ました。その結果、「インデックス用」が最適であることがわかりました。

int size = list.size();
for (int index = 0; index < size; index++) {
    Object object = list.get(index);
    ...
}

彼らはまた、ビデオの最後に自分のプラットフォームで同様のテストを行うことを提案しています.

于 2016-04-07T01:15:45.590 に答える
0

3 番目のバリアントは 7 よりも高速です。これは、配列が具体化された型であり、JVM が正しい値へのポインターを割り当てる必要があるためです。ただし、コレクションを反復処理すると、コンパイラーは消去のために追加のキャストを実行できます。実際、コンパイラはこのキャストを汎用コードに挿入して、非推奨の生の型をできるだけ早く使用するなどのダーティ ハックを特定します。

PSこれは単なる推測です。実際、コンパイラと JIT コンパイラは任意の最適化 (実行時でも JIT) を実行でき、結果は JVM のバージョンやベンダーなどの特定の詳細に依存すると思います。

于 2012-08-02T20:21:09.813 に答える