web-dev-qa-db-ja.com

Javaゲームでガベージコレクションの遅延を回避するにはどうすればよいですか?(ベストプラクティス)

Java Androidプラットフォーム用のJava $ ===。たまにガベージコレクションの描画とインタラクションに問題があります。通常は10分の1秒未満ですが、非常に遅いデバイスでは200ミリ秒にもなることがあります。

私はddmsプロファイラー(Android SDKの一部)を使用して、メモリ割り当てがどこから来るかを検索し、内部の図面とロジックループからそれらを削除しています。

最悪の犯罪者は、次のような短いループでした。

for(GameObject gob : interactiveObjects)
    gob.onDraw(canvas);

ループが実行されるたびに、iteratorが割り当てられていました。現在、オブジェクトに配列(ArrayList)を使用しています。内側のループにツリーまたはハッシュが必要な場合は、Javaコレクションフレームワークを使用する代わりに、余分なガベージコレクションを買う余裕がないので、注意するか、再実装する必要があることを知っています。優先キューを見ているときにそれが起こるかもしれません。

Canvas.drawTextを使用してスコアと進行状況を表示したい場合にも問題があります。これは悪いです、

canvas.drawText("Your score is: " + Score.points, x, y, Paint);

Stringschar配列、およびStringBuffersが全体に割り当てられて機能するためです。テキスト表示項目がいくつかあり、フレームを1秒間に60回実行すると、追加が始まり、ガベージコレクションのしゃっくりが増えます。ここでの最良の選択は、char[]配列を保持し、intまたはdoubleを手動でデコードし、文字列を先頭と末尾に連結することだと思います。もっときれいなものがあるかどうか聞きたいです。

これを扱っている人が他にもいるに違いない。どのようにそれを処理しますか?JavaまたはAndroidでインタラクティブに実行するために発見した落とし穴とベストプラクティスは何ですか?これらのGCの問題は、手動のメモリ管理を見逃すには十分ですが、それほどではありませんずっと。

62
Brian

私はJavaモバイルゲーム...に取り組んでいます。GC 'オブジェクトを回避する最善の方法(順番にshallある時点でGCをトリガーし、 shallゲームのperfを殺す)は、そもそもメインゲームループでそれらを作成しないようにすることです。

これに対処する「クリーンな」方法はありません。まず例を示します...

通常、画面には(50,25)、(70,32)、(16,18)、(98,73)の4つのボールがあります。さて、ここに抽象化があります(この例のために簡略化しています):

n = 4;
int[] { 50, 25, 70, 32, 16, 18, 98, 73 }

消える2番目のボールを「ポップ」すると、int []は次のようになります。

n = 3
int[] { 50, 25, 98, 73, 16, 18, 98, 73 }

(4番目のボール(98,73)を「クリーニング」することすら気にしていないことに注意してください。残ったボールの数を単純に追跡します)。

残念ながら、オブジェクトの手動追跡。これは、ほとんどの現在のパフォーマンスの良いJavaモバイルデバイスで公開されているゲームで行われています。

文字列については、次のようにします。

  • ゲームの初期化時に、drawText(...)only onceBufferedImage[10]配列に保存する0〜9の数字を使用して事前描画します。
  • ゲームの初期化時に、1回だけ事前描画"あなたのスコアは:"
  • "あなたのスコア:"を再描画する必要がある場合(たとえば透明なので)、事前に保存されたBufferedImageから再描画します
  • ループを使用して、スコアの桁を計算し、"Your score is:"の後に、すべての桁を手動で1つずつ(BufferedImage[10]から対応する桁(0から9)をコピーすることにより)事前に保存した場所。

これにより、両方の長所が得られます。drawtext(...)フォントを再利用し、メインループ中にオブジェクトをまったく作成しませんでした(alsoを回避したため) drawtext(...)の呼び出しmayそれ自体が非常にうまく生成されます。

この別の「利点」"ゼロオブジェクト作成描画スコア"は、フォントの注意深い画像キャッシュと再利用が実際にはないことです"手動オブジェクト割り当て/割り当て解除"、それは本当に丁寧なキャッシング。

これは「クリーン」ではなく、「グッドプラクティス」ではありませんが、最高のモバイルゲーム(Uniwarなど)で行われている方法です。

そしてそれは速いです。速くてオブジェクトの作成を含むanythingよりも高速です。

PS:実際、いくつかのモバイルゲームを注意深く見ると、多くの場合、フォントは実際にはシステム/ Javaフォントではなく、各ゲーム用に特別に作成されたピクセル完璧なフォントであることがわかります(ここでは、システム/ Javaフォントをキャッシュする方法ですが、明らかに、ピクセルパーフェクト/ビットマップフォントをキャッシュ/再利用することもできます。

56
SyntaxT3rr0r

少なくとも一種の_String.format_の独自のガベージフリーバージョンを作成しました。ここにあります: http://Pastebin.com/s6ZKa3mJ (ドイツ語のコメントはご容赦ください)。

次のように使用します。

_GFStringBuilder.format("Your score is: % and your name is %").eat(score).eat(name).result
_

すべてが_char[]_配列に書き込まれます。すべてのゴミを取り除くために、整数から文字列への変換を手動で(桁ごとに)実装する必要がありました。

それとは別に、可能な場合はSparseArrayを使用します。これは、HashMapArrayListなどのすべてのJavaデータ構造がプリミティブ型を処理するためのボックス化intIntegerにボックス化するたびに、このIntegerオブジェクトをGCでクリーンアップする必要があります。

7
David Scherfgen

提案されているようにテキストを事前にレンダリングしたくない場合、drawTextCharSequenceを受け入れます。つまり、独自のスマートな実装を作成できます。

final class PrefixedInt implements CharSequence {

    private final int prefixLen;
    private final StringBuilder buf;
    private int value; 

    public PrefixedInt(String prefix) {
        this.prefixLen = prefix.length();
        this.buf = new StringBuilder(prefix);
    }

    private boolean hasValue(){
        return buf.length() > prefixLen;
    }

    public void setValue(int value){
        if (hasValue() && this.value == value) 
            return; // no change
        this.value = value;
        buf.setLength(prefixLen);
        buf.append(value);
    }


    // TODO: Implement all CharSequence methods (including 
    // toString() for prudence) by delegating to buf 

}

// Usage:
private final PrefixedInt scoreText = new PrefixedInt("Your score is: ");
...
scoreText.setValue(Score.points);
canvas.drawText(scoreText, 0, scoreText.length(), x, y, Paint);

現在、スコアを描画しても割り当ては発生しません(ただし、bufs内部配列を成長させる必要がある最初の1回または2回、およびdrawTextの上限を除く)。

4
gustafc

GCの一時停止を回避することが重要な状況では、一時停止が重要でないことがわかっている時点で意図的にGCをトリガーすることを使用できます。たとえば、ゲームの終わりにゴミの多い「showScores」機能が使用されている場合、スコア画面の表示から次のゲームの開始までの200ミリ秒の遅延によってユーザーが気を散らされることはありません... System.gc()スコア画面がペイントされたら。

ただし、このトリックに頼る場合は、GCの一時停止が迷惑にならないポイントでのみ注意する必要があります。また、ハンドセットのバッテリーを消耗する心配がある場合は、それをしないでください。

また、マルチユーザーアプリケーションや非対話型アプリケーションでは実行しないでください。これにより、アプリケーションの全体的な実行速度が低下する可能性が高くなります。

3
Stephen C

イテレータの割り当てに関しては、ArrayListのイテレータを避けるのは簡単です。の代わりに

for(GameObject gob : interactiveObjects)
    gob.onDraw(canvas);

あなたはただできる

for (int i = 0; i < interactiveObjects.size(); i++) {
    interactiveObjects.get(i).onDraw();
}
2
user1410657