1

ユーザーが入力する単純な数学関数をグラフ化する Android アプリを作成しようとしています (基本的にグラフ電卓)。すべての onDraw 呼び出しには、毎秒数百回の算術評価が必要です (グラフを生成するために画面にプロットされます)。私のコードが式を評価すると、プログラムの速度が大幅に低下します。組み込みメソッドが式を評価すると、アプリは問題なく実行されます。

「LogCat」によると、ガベージ コレクションは 1 秒あたり約 12 回発生し、そのたびに約 15 ミリ秒間アプリが一時停止し、毎秒数百ミリ秒相当のフリーズが発生します。これが問題だと思います。

これは、私のエバリュエーター関数の蒸留バージョンです。評価される式は「postfixEquation」という名前で、文字列 ArrayList の「リスト」はプロセスの最後に最終的な答えを保持します。使用可能な数字と記号を格納する「数字」と「演算子」という名前の 2 つの文字列配列もあります。

String evaluate(String[] postfixEquation) {

    list.clear();

    for (int i = 0; i < postfixEquation.length; i++) {

        symbol = postfixEquation[i];

        // If the first character of our symbol is a digit, our symbol is a numeral
        if (Arrays.asList(digits).contains(Character.toString(symbol.charAt(0)))) {

            list.add(symbol);

            } else if (Arrays.asList(operators).contains(symbol))  {

                // There must be at least 2 numerals to operate on
                if (list.size() < 2) {
                    return "Error, Incorrect operator usage.";
                }

                // Operates on the top two numerals of the list, then removes them 
                // Adds the answer of the operation to the list
                firstItem = Double.parseDouble(list.get(list.size() - 1));
                secondItem = Double.parseDouble(list.get(list.size() - 2));
                list.remove(list.size() - 1);
                list.remove(list.size() - 1);

                if (symbol.equals(operators[0])){ 
                    list.add( Double.toString(secondItem - firstItem) );  
                } else if (symbol.equals(operators[1])) {
                    list.add( Double.toString(secondItem + firstItem) );
                } else if (symbol.equals(operators[2])) {
                    list.add( Double.toString(secondItem * firstItem) );
                } else if (symbol.equals(operators[3])) {
                    if (firstItem != 0) {
                        list.add( Double.toString(secondItem / firstItem) );
                    } else {
                        return "Error, Dividing by 0 is undefined.";
                    }
                } else {
                    return "Error, Unknown symbol '" + symbol + "'.";
                }
            }
        }

    // The list should contain a single item, the final answer 
    if (list.size() != 1) {
        return "Error, " + list has " + list.size() + " items left instead of 1.";
    }

    // All is fine, return the final answer
    return list.get(0);
}

操作で使用される数値はすべて文字列です。1 つの配列内に複数の型 (つまり、文字列と倍精度型) を保持できるかどうかわからなかったため、"Double.parseDouble" と "Double.toString" 呼び出しが横行しています。

ここで発生するガベージ コレクションの量を減らすにはどうすればよいでしょうか。

それが助けになる場合は、次の手順を使用して後置式を評価しています: http://scriptasylum.com/tutorials/infix_postfix/algorithms/postfix-evaluation/index.htm。私は何週間も何週間もこの問題を乗り越えることができませんでした. どんな助けでも大歓迎です。ありがとう。

4

4 に答える 4

3

Java でのタイトなループのルールは、何も割り当てないことです。このように頻繁に GC コレクションが表示されているという事実は、これを証明しています。

で計算を行ってDoubleから、 に変換しているようStringです。そうしないでください。大量の文字列を作成してから破棄するため、パフォーマンスが低下します(さらに、文字列と倍精度の間で頻繁に変換します)。を維持しArrayDeque<Double>てスタックとして使用するだけです。これにより、おそらくパフォーマンスも低下させる配列のサイズ変更を行う必要がなくなります。

入力方程式をプリコンパイルします。すべての入力操作をインスタンスに変換しenumます。それらは比較が高速で (switchステートメントを取得するだけです)、使用するメモリも少なくて済みます。double を処理する必要がある場合は、ジェネリックObjectコンテナーと を使用するか、操作と のinstanceof両方を含むコンテナー クラスを使用します。プリコンパイルにより、タイトなループで高価なテストを実行する必要がなくなります。enumdouble

これらのことを行うと、ループは確実に飛ぶはずです。

于 2012-09-27T10:27:48.520 に答える
0

おそらくあなたのリスト操作がこの問題の原因です。リストには内部的に配列があり、リストにあるデータの量に応じて拡張/縮小されます。したがって、大量の追加と削除をランダムに行うには、ガベージコレクションが非常に必要になります。

これを回避するための解決策はList、問題に適切な実装を使用し、内部配列のサイズ変更を回避し、未使用の要素を削除する代わりにマークするために、最初にリストに十分なスペースを割り当てることです。

フリーズの症状は、UIThreadで計算を行っているためです。アプリをフリーズさせたくない場合はAsyncTask、別のスレッドで計算を行うためにチェックすることをお勧めします。

PS:また、そこでいくつかの役に立たない操作をしているように見えます...なぜparseDouble() secondItemですか?

于 2012-09-27T10:23:22.620 に答える
0

ただし、多くの文字列を使用しています。これは当てはまらないかもしれませんが、Java は String でファンキーなことを行うため、常に確認できることの 1 つです。出力時に文字列を double に変換する必要がある場合は、かなりのオーバーヘッドが発生します。

データを文字列として保存する必要がありますか? (答えは実際には「はい」かもしれないことに注意してください) 一時文字列を頻繁に使用すると、実際にはガベージ コレクターが頻繁に起動される可能性があります。

時期尚早の最適化に注意してください。プロファイラーと関数を行ごとに実行すると役立ちます

于 2012-09-27T13:35:29.493 に答える
0

15 ミリ秒の一時停止は UI スレッドで発生していないため、パフォーマンスに大きな影響を与えることはありません。メソッドの実行中に UI が一時停止している場合は、別のスレッドで (AsyncTask を使用して) 実行することを検討してください。

ガベージ コレクションを減らすには、ループ内で割り当てられるメモリの量を減らす必要があります。

私は見ることをお勧めします:

  1. ループの外で Arrays.asList 関数を実行する (理想的には、コンストラクターや静的コンストラクターなど、一度だけ実行される場所)
  2. リストが LinkedList の場合は、ArrayList に変更することを検討してください
  3. List が ArrayList の場合は、サイズを変更する必要がないように、十分な容量で初期化してください。
  4. リストに文字列ではなくオブジェクトを格納することを検討してください。そうすれば、シンボルとダブルの両方を格納でき、ダブルから文字列に前後に変換する必要はありません。
  5. 適切なパーサーを作成することを検討してください (ただし、これにはさらに多くの作業が必要です)
于 2012-09-27T11:11:12.213 に答える