10

サイズが 24 MB (1 行あたり平均 17 文字) の 140 万行の大きなテキスト ファイルを読み込んでいます。

私は Delphi 2009 を使用しており、ファイルは ANSI ですが、読み取り時に Unicode に変換されるため、変換後のテキストのサイズは 48 MB であると言えます。

(編集:もっと簡単な例を見つけました...)

このテキストを単純な StringList にロードしています。

  AllLines := TStringList.Create;
  AllLines.LoadFromFile(Filename);

私は、データ行が 48 MB よりも多くのメモリを必要とするように見えることを発見しました。

実際、155 MB のメモリを使用しています。

Delphi が 48 MB または 60 MB を使用していても、メモリ管理のオーバーヘッドを考慮して問題ありません。しかし、155 MB は過剰に思えます。

これは StringList の障害ではありません。以前に行をレコード構造にロードしようとしましたが、同じ結果 (160 MB) が得られました。

Delphi または FastMM メモリ マネージャが、文字列を格納するために必要なメモリ量の 3 倍の量を使用する原因が何なのか、私にはわかりません。ヒープ割り当てはそれほど非効率的ではありませんよね?

私はこれをデバッグし、できる限り調査しました。なぜこれが起こっているのかについてのアイデア、または過剰な使用を減らすのに役立つアイデアは大歓迎です.

注: この「小さい」ファイルを例として使用しています。私は実際に 320 MB のファイルをロードしようとしていますが、Delphi は 2 GB を超える RAM を要求しており、この過剰な文字列要件のためにメモリが不足しています。

補遺: Marco Cantu が、Delphi と Unicode に関するホワイト ペーパーを発表しました。Delphi 2009 では、文字列あたりのオーバーヘッドが 8 バイトから 12 バイトに増加しました (さらに、文字列への実際のポインタの場合はさらに 4 バイト)。17x2 = 34 バイト ラインごとに 16 バイトが追加されると、ほぼ 50% が追加されます。しかし、オーバーヘッドが 200% を超えています。余分な 150% は何になるでしょうか?


成功!!皆様のご提案に感謝いたします。みなさん、考えさせられました。しかし、彼が尋ねたので、私は Jan Goyvaerts に答えを与えなければなりません:

...なぜ TStringList を使用しているのですか? ファイルは本当に別の行としてメモリに格納する必要がありますか?

その結果、24 MB のファイルを 140 万行の StringList としてロードする代わりに、プログラムが認識している自然なグループに行をグループ化できるという解決策にたどり着きました。そのため、127,000 行が文字列リストに読み込まれました。

現在、各行の平均文字数は 17 文字ではなく 190 文字です。StringList 行ごとのオーバーヘッドは同じですが、行数が大幅に少なくなりました。

これを 320 MB のファイルに適用すると、メモリが不足することはなくなり、1 GB 未満の RAM にロードされるようになりました。(そして、読み込みに約10秒しかかかりません。これはかなり良いです!)

グループ化された行を解析するために少し余分な処理が必要になりますが、各グループのリアルタイム処理では目立たないはずです。

(念のために言っておきますが、これは家系図プログラムであり、32 ビット アドレス空間に約 100 万人のすべてのデータを 30 秒以内にロードできるようにするために必要な最後のステップかもしれません。インデックスをデータに追加するための 20 秒のバッファがまだあります。これは、データの表示と編集を可能にするために必要です。)

4

8 に答える 8

10

あなたはここであなたの質問に答えるように私に個人的に頼んだ. メモリ使用量がこれほど高くなった正確な理由はわかりませんが、TStringList はファイルをロードするだけでなく、多くのことを行うことを覚えておく必要があります。これらの各手順にはメモリが必要であり、メモリの断片化が発生する可能性があります。TStringList は、ファイルをメモリにロードし、Ansi から Unicode に変換し、行ごとに 1 つの文字列に分割し、それらの行を何度も再割り当てされる配列に詰め込む必要があります。

あなたへの私の質問は、なぜ TStringList を使用しているのですか? ファイルは本当に別の行としてメモリに格納する必要がありますか? メモリ内のファイルを変更しますか、それともその一部を表示するだけですか? ファイルを 1 つの大きなチャンクとしてメモリに保持し、必要な部分に一致する正規表現を使用して全体をスキャンすると、個別の行を保存するよりもメモリ効率が高くなります。

また、ファイル全体を Unicode に変換する必要がありますか? アプリケーションは Unicode ですが、ファイルは Ansi です。私の一般的な推奨事項は、できるだけ早く Ansi 入力を Unicode に変換することです。そうすることで CPU サイクルが節約されるからです。しかし、Ansi データのまま 320 MB の Ansi データがある場合、メモリ消費がボトルネックになります。ファイルを Ansi としてメモリに保持し、ユーザーに表示する部分のみを Ansi として変換してみてください。

320 MB のファイルが特定の情報を抽出するデータ ファイルではなく、変更したいデータ セットである場合は、それをリレーショナル データベースに変換することを検討し、データベース エンジンに膨大なデータ セットの管理方法を任せます。限られたRAMで。

于 2008-11-23T10:39:45.053 に答える
8

元のレコードに AnsiString を使用した場合はどうなるでしょうか。それはすぐにそれを半分に切りますか?Delphi のデフォルトが UnicodeString であるからといって、それを使用する必要があるわけではありません。

さらに、各文字列の長さが (1 ~ 2 文字以内で) 正確にわかっている場合は、短い文字列を使用してさらに数バイトを削ったほうがよい場合があります。

あなたがやろうとしていることを達成するためのより良い方法があるかもしれないかどうか、私は興味があります. 320 MB のテキストをメモリにロードすることは、たとえ 320 MB しか必要としないようにダウンさせることができたとしても、最善の解決策ではない可能性があります。

于 2008-11-23T07:19:09.573 に答える
6

私はDelphi2009を使用しており、ファイルはANSIですが、読み取るとUnicodeに変換されるため、変換後のテキストのサイズは48MBであると言っても過言ではありません。

申し訳ありませんが、私はこれをまったく理解していません。プログラムをUnicodeにする必要がある場合は、ファイルが「ANSI」(WIN1252やISO8859_1などの文字セットが必要)であることは間違いありません。最初にそれをUTF8に変換します。ファイルに128以上の文字が含まれていない場合、ファイルは変更されません(同じサイズになることもあります)が、将来に備えています。

これで、UTF8文字列にロードできます。これにより、メモリ消費量が2倍になることはありません。画面に同時に表示される可能性のあるいくつかの文字列からDelphiUnicode文字列へのオンザフライ変換は遅くなりますが、メモリフットプリントが小さいため、プログラムはほとんど(無料)のないシステムではるかに優れたパフォーマンスを発揮します。メモリー。

これで、プログラムがTStringListでメモリを消費しすぎる場合は、プログラムでいつでもTStringsまたはIStringsを使用でき、IStringsを実装するかTStringsを継承し、すべての行をメモリに保持しないクラスを作成できます。頭に浮かぶいくつかのアイデア:

  1. ファイルをTMemoryStreamに読み込み、行の最初の文字へのポインターの配列を維持します。文字列を返すのは簡単です。行の先頭と次の行の先頭の間に適切な文字列を返すだけで、CRとNLが削除されます。

  2. それでも大量のメモリを消費する場合は、TMemoryStreamをTFileStreamに置き換え、charポインタの配列を維持しませんが、行のファイルオフセットの配列が開始されます。

  3. メモリマップトファイルにWindowsAPI関数を使用することもできます。これにより、ファイルオフセットの代わりにメモリアドレスを操作できますが、最初のアイデアほど多くのメモリを消費しません。

于 2008-11-23T14:25:11.837 に答える
4

デフォルトでは、Delphi 2009 の TStringList はファイルを ANSI として読み取ります。ただし、ファイルを別のものとして識別するバイト オーダー マークがない限り、または LoadFromFile のオプションの 2 番目のパラメータとしてエンコーディングを指定した場合は除きます。

したがって、TStringList が思ったより多くのメモリを消費している場合は、別のことが起こっています。

于 2008-11-23T08:29:37.000 に答える
3

万が一、sourceforge の FastMM ソースと FullDebugMode を定義してプログラムをコンパイルしていませんか? その場合、FastMM は実際には未使用のメモリ ブロックを解放していないため、問題が説明されます。

于 2008-11-23T09:21:45.990 に答える
1

プログラムが使用しているメモリの量を Windows に頼っていますか? Delphi アプリが使用するメモリを誇張することで有名です。

ただし、コードで余分なメモリが大量に使用されていることがわかります。

レコード構造は 20 バイトです。行ごとにそのようなレコードが 1 つある場合、テキストよりもレコードのデータの方が多くなります。

さらに、文字列には固有の 4 バイトのオーバーヘッド (さらに 25%) があります。

Delphi のヒープ処理にはある程度の割り当ての粒度があると思いますが、現時点ではそれがどのようなものかは思い出せません。8 バイト (空きブロックのリンクされたリストの 2 つのポインター) でも、さらに 25% を見ています。

すでに 150% 以上の増加に達していることに注意してください。

于 2008-11-23T05:39:14.050 に答える
1

その一部は、ブロック割り当てアルゴリズムである可能性があります。リストが大きくなるにつれて、各チャンクに割り当てられるメモリの量が増え始めます。長い間見ていませんでしたが、メモリが不足するたびに、最後に割り当てられた量が2倍になるようなものだと思います。大規模なリストを処理し始めると、割り当ても最終的に必要となるよりもはるかに大きくなります。

編集- lkessler が指摘したように、この増加は実際には 25% にすぎませんが、それでも問題の一部と見なす必要があります。転換点を超えたばかりの場合は、使用されていないリストに割り当てられた膨大なメモリ ブロックが存在する可能性があります。

于 2008-11-24T18:46:47.160 に答える
0

なぜその量のデータをTStringListにロードするのですか?リスト自体にはいくらかのオーバーヘッドがあります。たぶんTTextReaderがあなたを助けるかもしれません。

于 2008-11-24T15:48:10.793 に答える