0

私のアプリケーションは、SD カードから ~100MB のファイルを解析することから始めます。解析には数分かかります。概観すると、私の PC では、同じファイルの解析に数秒かかります。

MatcherPatternを使用して単純にパーサーを実装することから始めましたが、DDMS によると、時間の 90% が正規表現の計算に費やされていました。そして、ファイルの解析に 30 分以上かかりました。パターンはとてつもなくシンプルで、行は次のように構成されています。

ID (a number) <TAB> LANG (a 3-to-5 character string) <TAB> DATA (the rest)

String.splitを試してみることにしました。おそらく、この関数自体が正規表現を使用している可能性があるため、大幅な改善は見られませんでした。その時点で、私はパーサーを完全に書き直すことに決め、最終的には次のようになりました。

protected Collection<Sentence> doInBackground( Void... params ) {
    BufferedReader reader = new BufferedReader( new FileReader( sentenceFile ) );

    String currentLine = null;
    while ( (currentLine = reader.readLine()) != null ) {
        treatLine( currentLine, allSentences );
    }

    reader.close();
    return allSentences;
}

private void treatLine( String line, Collection<Sentence> allSentences ) {
    char[] str = line.toCharArray();

    // ...
    // treat the array of chars into an id, a language and some data

    allSentences.add( new Sentence( id, lang, data ) );
}

そして、私は大きな後押しに気づきました。30分ではなく数分かかりました。しかし、これでは満足できなかったので、プロファイリングを行ったところ、ボトルネックがBufferedReader.readLineであることがわかりました。私は疑問に思いました: IO バウンドである可能性がありますが、実際には必要のない中間バッファーを埋めるのに多くの時間がかかる可能性もあります。そこで、FileReader を直接使用して全体を書き直しました。

protected Collection<Sentence> doInBackground( Void... params ) {
    FileReader reader = new FileReader( sentenceFile );
    int currentChar;
    while ( (currentChar = reader.read()) != -1 ) {
        // parse an id
        // ...            

        // parse a language
        while ( (currentChar = reader.read()) != -1 ) {
            // do some parsing stuff
        }

        // parse the sentence data
        while ( (currentChar = reader.read()) != -1 ) {
            // parse parse parse
        }

        allSentences.add( new Sentence( id, lang, data ) );
    }

    reader.close();
}

そして、パフォーマンスが非常に悪いことに気づき、かなり驚きました。明らかに、ほとんどの時間はFileReader.readで費やされます。char を読み取るだけでもかなりのコストがかかると思います。

今、私は少しインスピレーションを失っています。ヒントはありますか?

4

6 に答える 6

2

パフォーマンスを向上させる可能性のある別のオプションは、 のInputStreamReader周りに を使用することFileInputStreamです。自分でバッファリングを行う必要がありますが、それによってパフォーマンスが確実に向上する可能性があります。詳細については、このチュートリアルを参照してください。ただし、むやみに従わないでください。たとえば、char 配列を使用している場合、char 配列をバッファーとして使用できます (treatLine()改行に達したときにそれを送信します)。

さらに別の提案は、実際にThread直接使用することです。ドキュメンテーションAsyncTask 言う(私のイントネーション):

AsyncTask は、Thread と Handler のヘルパー クラスとして設計されており、一般的なスレッド フレームワークを構成するものではありません。AsyncTasks は、理想的には短い操作 (せいぜい数秒) に使用する必要があります。スレッドを長時間実行し続ける必要がある場合は、次のような java.util.concurrent パッケージで提供されるさまざまな API を使用することを強くお勧めします。 Executor、ThreadPoolExecutor、および FutureTask。

また、より高速な SD カードを入手することも確かに役立ちます。これがおそらく、デスクトップよりもはるかに遅い主な理由です。通常の HD はおそらく 60 MB/秒、遅い SD カードは 2 MB/秒で読み取ることができます。

于 2013-08-01T00:35:27.210 に答える
1

BufferedReader を削除すると、さらに悪化しました。もちろん。「中間バッファをいっぱいにする」必要があります。ディレクトリで文字ごとに実行している8192のシステムコールのうち8191を節約しますFileReader。バッファ I/O は常に高速です。なぜあなたがそうでないと思ったのか、私にはわかりません。

于 2013-08-01T00:52:24.783 に答える
1

@EJP が述べたように、BufferedReader を使用する必要があります。しかし、より基本的には、PC ではなく、モバイル デバイスで実行しています。フラッシュの読み取り速度は PC の速度には遠く及ばず、処理能力は 3.5 GHz で動作する 4 コア 8 スレッドの i7 の何分の 1 かであり、フラッシュと CPU の両方をフルスピードで実行するものについては考慮していません。デバイスのバッテリー寿命に影響を与えます。

したがって、自問すべき本当の質問は、なぜアプリが 100 MB のデータを解析する必要があるのか​​ということです。また、起動時に毎回解析する必要がある場合、PC 上で解析するだけで、ユーザーが解析する必要がなくなるのはなぜでしょうか?

于 2013-08-01T01:26:00.183 に答える
0

allSentences は ArrayList ですか? もしそうなら、おそらくアイテムの数が多く、何度もサイズを変更する必要があります. 大容量でアレイを初期化してみてください。

各 ArrayList インスタンスには容量があります。容量は、リスト内の要素を格納するために使用される配列のサイズです。常に少なくともリスト サイズと同じ大きさです。要素が ArrayList に追加されると、その容量は自動的に増加します。成長ポリシーの詳細は、要素の追加には一定の償却時間コストがあるという事実以外は指定されていません。

アプリケーションは、ensureCapacity 操作を使用して多数の要素を追加する前に、ArrayList インスタンスの容量を増やすことができます。これにより、増分再割り当ての量が減る場合があります。配列リスト

他のあなたが試すことができると思います:

  • NDK を使用します。
  • @Anson Yaoが言ったように、バッファのサイズを大きくしてみてください
  • 関数呼び出しのオーバーヘッドを減らすために、treatLine 関数を削除します。
于 2013-08-01T01:35:03.730 に答える