メモリ マップ ファイルのパフォーマンス/安定性 - ネイティブまたは MappedByteBuffer - と普通の FileOutputStream

StackOverflow https://stackoverflow.com/questions/537295

質問

私は、永続化のためにフラット ファイル (プレーン テキスト) を使用するレガシー Java アプリケーションをサポートしています。アプリケーションの性質により、これらのファイルのサイズは 1 日あたり 100 MB に達する可能性があり、多くの場合、アプリケーションのパフォーマンスの制限要因はファイル IO です。現在、アプリケーションはプレーンな java.io.FileOutputStream を使用してデータをディスクに書き込みます。

最近、ネイティブ コード (C/C++) で実装され、JNI 経由でアクセスされるメモリ マップ ファイルを使用すると、パフォーマンスが向上すると主張する開発者が数人いました。ただし、FileOutputStream はすでにそのコア メソッドにネイティブ メソッドを使用しています (つまり、write(byte[])) であるため、確実なデータや少なくとも事例証拠がなければ、根拠の薄い仮定であるように見えます。

これに関していくつか質問があります:

  1. この主張は本当に真実なのでしょうか?メモリマップされたファイルになります いつもJavaのFileOutputStreamと比較して、より高速なIOを提供しますか?

  2. FileChannelからアクセスされたクラスMappedByteBufferは、JNIを介してアクセスされるネイティブメモリマッピングファイルライブラリと同じ機能を提供しますか?JNIソリューションの使用につながる可能性のあるMappedByteBufferが不足しているものは何ですか?

  3. 生産アプリケーションでディスクIOにメモリマップされたファイルを使用するリスクは何ですか?つまり、最小限の再起動で継続的な稼働時間があるアプリケーション(月に1回、最大)です。生産アプリケーションからの実際の逸話(Javaまたはその他)が望ましい。

質問 #3 は重要です - この質問は自分で答えることができます 部分的に 上記のさまざまなオプションを使用して IO をパフォーマンステストする「おもちゃ」アプリケーションを作成することによって、SO に投稿することで、現実世界の逸話やデータを理解できることを期待しています。

[編集] 明確化 - 毎日の操作では、アプリケーションは 100MB から 1 GB までのサイズの複数のファイルを作成します。合計すると、アプリケーションは 1 日に数ギガのデータを書き出す可能性があります。

役に立ちましたか?

解決

書き込み中にデータがどのようにバッファリングされるかを調べることで、処理を少し高速化できる場合があります。予想されるデータ書き込みパターンについてのアイデアが必要となるため、これはアプリケーション固有になる傾向があります。データの一貫性が重要な場合、ここでトレードオフが発生します。

アプリケーションから新しいデータをディスクに書き出すだけの場合、メモリ マップド I/O はおそらくあまり役​​に立ちません。カスタムコーディングされたネイティブソリューションに時間を投資する理由はわかりません。これまでに提供した内容からすると、アプリケーションには複雑すぎるように思えます。

本当に優れた I/O パフォーマンス (または単に O パフォーマンス) が必要であると確信している場合は、調整されたディスク アレイなどのハードウェア ソリューションを検討します。ビジネスの観点からは、ソフトウェアの最適化に時間を費やすよりも、より多くのハードウェアを投入するほうが、多くの場合、何倍も費用対効果が高くなります。また、通常は実装が早く、信頼性も高くなります。

一般に、ソフトウェアの過剰な最適化には多くの落とし穴があります。新しいタイプの問題をアプリケーションに導入することになります。メモリの問題や GC スラッシングが発生し、メンテナンスやチューニングがさらに必要になる可能性があります。最悪の点は、これらの問題の多くは実稼働前にテストするのが難しいことです。

それが私のアプリだったら、おそらくバッファリングを調整した FileOutputStream を使い続けるでしょう。その後は、より多くのハードウェアを投入するという昔ながらの解決策を使用します。

他のヒント

メモリ マップド I/O によってディスクの動作が高速化されるわけではありません (!)。直線的なアクセスの場合、それは少し無意味に思えます。

NIO マップされたバッファーは本物です (合理的な実装に関する通常の警告)。

他の NIO 直接割り当てバッファーと同様、バッファーは通常のメモリではないため、効率的に GC が実行されません。それらを多数作成すると、Java ヒープが不足することなくメモリ/アドレス空間が不足する場合があります。これは、プロセスの実行時間が長い場合には明らかに懸念事項です。

私の経験から言えば、メモリ マップされたファイルは、リアルタイムと永続性の両方のユースケースにおいて、プレーン ファイル アクセスよりもはるかに優れたパフォーマンスを発揮します。私は主に Windows 上で C++ を使用して作業してきましたが、Linux のパフォーマンスも同様であり、いずれにしても JNI を使用する予定であるため、問題に当てはまると思います。

メモリ マップド ファイルに基づいて構築された永続エンジンの例については、を参照してください。 メタキット. 。私はオブジェクトがメモリマップされたデータに対する単純なビューであり、エンジンがカーテンの後ろですべてのマッピング作業を処理するアプリケーションでこれを使用しました。これは高速かつメモリ効率が高く (少なくとも以前のバージョンで使用されていたような従来のアプローチと比較して)、コミット/ロールバック トランザクションを無料で実現できました。

別のプロジェクトでは、マルチキャスト ネットワーク アプリケーションを作成する必要がありました。連続するパケット損失の影響を最小限に抑えるために、データはランダムな順序で送信されました (FEC およびブロッキング スキームと組み合わせて)。さらに、データはアドレス空間をはるかに超える可能性があるため (ビデオ ファイルは 2GB を超えていました)、メモリ割り当ては問題外でした。サーバー側では、ファイル セクションがオンデマンドでメモリ マップされ、ネットワーク層がこれらのビューからデータを直接選択しました。その結果、メモリ使用量は非常に低くなりました。受信側では、パケットが受信される順序を予測する方法がなかったため、ターゲット ファイル上で限られた数のアクティブなビューを維持する必要があり、データはこれらのビューに直接コピーされました。パケットをマップされていない領域に配置する必要がある場合、最も古いビューがマップされておらず (最終的にはシステムによってファイルにフラッシュされ)、宛先領域上の新しいビューに置き換えられます。特にシステムがバックグラウンド タスクとしてデータをコミットする際に優れた仕事をし、リアルタイムの制約が容易に満たされたため、パフォーマンスは際立っていました。

それ以来、システムはデータをいつどのように書き込む必要があるかについてユーザー空間アプリケーションよりも多くのことを知っているため、精巧に作られた最高のソフトウェア スキームでもメモリ マップト ファイルを使用したシステムのデフォルト I/O ポリシーを超えることはできないと確信しています。また、知っておくべき重要なことは、大規模なデータを扱う場合にはメモリ マッピングが必須であるということです。データは決して割り当てられず (したがってメモリを消費します)、アドレス空間に動的にマッピングされ、システムの仮想メモリ マネージャによって管理されるからです。常にヒープよりも高速です。したがって、システムは常にメモリを最適に使用し、アプリケーションに影響を与えることなく、必要なときにいつでもアプリケーションの背後でデータをコミットします。

それが役に立てば幸い。

ポイント 3 に関しては、マシンがクラッシュし、ディスクにフラッシュされなかったページが存在する場合、それらのページは失われます。もう 1 つは、アドレス スペースの無駄です。ファイルをメモリにマッピングすると、アドレス スペースが消費されます (そして連続した領域が必要です)。32 ビット マシンでは、少し制限があります。しかし、約 100MB と言っているので、問題はないはずです。そしてもう 1 つ、mmaped ファイルのサイズを拡張するにはいくつかの作業が必要です。

ところで、 このSOディスカッション いくつかの洞察も得られます。

私はやった 勉強 ここで書き込みパフォーマンスを生のパフォーマンスと比較します。 ByteBuffer への書き込みパフォーマンスとの比較 MappedByteBuffer. 。メモリマップされたファイルは OS でサポートされており、ベンチマークの数値からもわかるように、書き込みレイテンシーは非常に良好です。FileChannel を介して同期書き込みを実行すると、約 20 倍遅くなるため、人々は常に非同期ログを実行します。私の研究では、ロックフリーでガベージフリーのキューを介して非同期ロギングを実装し、生のパフォーマンスに非常に近い究極のパフォーマンスを実現する方法の例も示しています。 ByteBuffer.

書き込むバイト数が少ないほど高速になります。gzipoutputstream でフィルタリングした場合、あるいはデータを ZipFiles または JarFiles に書き込んだ場合はどうなるでしょうか?

上で述べたように、NIO (別名 NIO) を使用します。新しいIO)。新しい新しい IO も登場します。

RAID ハードドライブ ソリューションを適切に使用すれば役に立ちますが、それは面倒です。

データを圧縮するというアイデアがとても気に入っています。gzipoutputstream を試してみましょう!CPU が対応できればスループットが 2 倍になります。今や標準となったダブルコアマシンを活用できそうですよね。

-ストッシュ

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top