web-dev-qa-db-ja.com

共有メモリ用のファイルの使用IPC

私のアプリケーションには、ファイルにデータを書き込むプロセスが1つあり、要求の受信に応じて、そのデータの一部(一部)をネットワーク経由で要求プロセスに送信します。この質問の基本は、両方のプロセスが偶然同じホスト上にある場合に通信を高速化できるかどうかを確認することです。 (私の場合、プロセスはJavaですが、この議論はもっと広く適用できると思います。)

同じホスト上のJVM間で共有メモリIPCを共有する方法としてJavaのFileChannel.map()によって返されるMappedByteBuffersを使用するプロジェクトがいくつかあります(Chronicle Queue、Aeron IPC、等。)。

同一ホスト通信を高速化する1つのアプローチは、アプリケーションにそれらのテクノロジーの1つを使用して、データファイルに書き込むための既存のメカニズムと組み合わせて、または提供することにより、同一ホスト通信の要求応答経路を提供することです。通信とファイルへの書き込みの両方の統一された手段。

別のアプローチは、要求プロセスがデータファイルに直接アクセスできるようにすることです。

私は2番目のアプローチを好む傾向があります-正しいと仮定して-実装が容易であり、各リクエストのデータのコピーをコピー/送信するよりも効率的であるように思われます(既存の書き込みメカニズムを置き換えなかった場合)ファイルへ)。

基本的に、2つのプロセスが同じファイルにアクセスしたときに何が起こるかを正確に理解し、それを使用して通信します。具体的にはJava(1.8)およびLinux(3.10))。

私の理解では、2つのプロセスが同時に同じファイルを開いている場合、それらの間の「通信」は基本的に「共有メモリ」を介して行われるようです。

この質問はMappedByteBufferの使用によるパフォーマンスへの影響とは関係がないことに注意してください。マップされたバッファの使用、およびコピーとシステムコールの削減により、ファイルの読み取りと書き込みに比べてオーバーヘッドが削減される可能性が非常に高いです。アプリケーションに大幅な変更が必要になる場合があります。

これが私の理解です:

  1. Linuxがディスクからファイルをロードすると、そのファイルの内容がメモリ内のページにコピーされます。そのメモリ領域はページキャッシュと呼ばれます。私の知る限り、これは、Javaメソッド(FileInputStream.read()、RandomAccessFile.read()、FileChannel.read()、FileChannel.map())またはファイルの読み取りにはネイティブメソッドが使用されます(「free」にこだわり、「cache」値を監視しています)。
  2. 別のプロセスが同じファイルをロードしようとした場合(まだキャッシュに常駐している場合)、カーネルはこれを検出し、ファイルを再ロードする必要はありません。ページキャッシュがいっぱいになると、ページが追い出されます-ダーティなページがディスクに書き戻されます。 (ディスクへの明示的なフラッシュがある場合、ページは定期的にカーネルスレッドで書き戻されます)。
  3. すでにキャッシュに(大きな)ファイルがあると、パフォーマンスが大幅に向上します。これは、Javaメソッドを使用してそのファイルを開いたり読み取ったりする方法に基づく違いよりもはるかに大きくなります。
  4. Mmapシステムコール(C)またはFileChannel.map()(Java)を使用してファイルがロードされる場合、基本的に(キャッシュ内の)ファイルのページはプロセスのアドレススペースに直接ロードされます。他の方法を使用してファイルを開くと、ファイルはプロセスのアドレス空間にないページにロードされます。次に、そのファイルを読み書きするさまざまな方法で、それらのページから数バイトをプロセスのアドレス空間のバッファーにコピーします。 。そのコピーを回避することには明らかなパフォーマンス上の利点がありますが、私の質問はパフォーマンスに関係していません。

つまり、要約すると、私が正しく理解していれば、マッピングはパフォーマンスの利点を提供しますが、Linuxとページキャッシュの性質だけではまだ得られていない「共有メモリ」機能を提供しているようには見えません。

だから、私の理解がどこにあるのか教えてください。

ありがとう。

基本的に、2つのプロセスが同時に同じファイルを開いているときに何が起こるかを理解しようとしています。これを使用して、プロセス間の通信を安全かつ高性能に提供できるかどうかを考えています。

readおよびwrite操作を使用して通常のファイルを使用している場合(つまり、それらをメモリマッピングしない場合)、2つのプロセスはメモリを共有しません。

  • ファイルに関連付けられているJava Bufferオブジェクト内のユーザー空間メモリは、アドレス空間間で共有されません。
  • write syscallが行われると、データはcopied 1つのプロセスのアドレス空間のページからカーネル空間のページに変換されます。 (これらはcouldページキャッシュ内のページです。これはOS固有です。)
  • read syscallが行われると、データはコピーカーネルスペースのページから読み取りプロセスのアドレススペースのページになります。

そうする必要があります。リーダーとライターに関連付けられたオペレーティングシステムの共有ページが背後でバッファを処理する場合、それはセキュリティ/情報漏えいの穴になります。

  • リーダーは、write(...)を介してまだ書き込まれていないライターのアドレススペースのデータを見ることができます。
  • ライターは、リーダーが(仮説的に)読み取りバッファーに書き込んだデータを見ることができます。
  • メモリ保護の細分性はページであり、read(...)およびwrite(...)の細分性は単一であるので、メモリ保護を巧みに使用して問題に対処することはできません。バイト。

もちろん、ファイルの読み取りと書き込みを安全に使用して、2つのプロセス間でデータを転送できます。しかし、読者がライターが書き込んだデータ量を知ることができるプロトコルを定義する必要があります。そして、読者が知っているwhen作家が何かを書いたことがポーリングを伴う可能性があることを知っている。例えばファイルが変更されているかどうかを確認します。

これを通信「チャネル」でのデータのコピーだけの観点から見ると

  • メモリマップファイルでは、アプリケーションヒープオブジェクトからマップバッファーにデータをコピー(シリアル化)し、マップバッファーからアプリケーションヒープオブジェクトにもう一度(逆シリアル化)します。

  • 通常のファイルでは、2つの追加コピーがあります。1)書き込みプロセス(マップされていない)バッファーからカーネルスペースページ(ページキャッシュなど)へ、2)カーネルスペースページから読み取りプロセス(マップされていない)バッファーへ。

以下の記事では、従来の読み取り/書き込みとメモリマッピングで何が行われているのかを説明します。 (これはファイルのコピーと「ゼロコピー」のコンテキストにありますが、それは無視できます。)

参照:

0
Stephen C

私の質問は、Java(1.8)とLinux(3.10)で、共有メモリIPCを実装するためにMappedByteBufferが本当に必要なのか、または共通ファイルへのアクセスで同じ機能が提供されるのか、です。

whyによって異なります。共有メモリIPCを実装する必要があります。

共有メモリなしでIPCを明確に実装できます。例えばソケットの上。したがって、パフォーマンス上の理由でそうしない場合は、共有メモリIPCを実行する必要はまったくありません。

したがって、パフォーマンスはあらゆる議論の根幹にある必要があります。

Javaクラシックioまたはnio APIを介したファイルを使用したアクセスでは、共有メモリの機能やパフォーマンスは提供されません。

通常のファイルI/OまたはソケットI/Oと共有メモリIPCの主な違いは、前者はアプリケーションが明示的にreadおよびwrite syscallを送信して送信する必要があることですメッセージを受信します。これには追加のシステムコールが必要であり、カーネルがデータをコピーする必要があります。さらに、複数のスレッドがある場合は、各スレッドペアの間に個別の「チャネル」が必要か、共有チャネルを介して複数の「会話」を多重化するための何かが必要です。後者は、共有チャネルが並行性のボトルネックになる可能性があります。

これらのオーバーヘッドはLinuxページキャッシュに対してorthogonalであることに注意してください。

対照的に、共有メモリを使用して実装されたIPCでは、readおよびwrite syscallはなく、追加のコピー手順はありません。各「チャネル」は、マップされたバッファの個別の領域を単純に使用できます。 1つのプロセス内のスレッドがデータを共有メモリに書き込み、2番目のプロセスがほぼ即座にそれを見ることができます。

注意点は、プロセスが1)同期する必要があり、2)リーダーが古いデータを見ないようにするためにメモリバリアを実装する必要があることです。しかし、これらはどちらもシステムコールなしで実装できます。

ウォッシュアップでは、メモリマップファイルを使用する共有メモリIPCは、従来のファイルやソケットを使用するよりも高速です。そのため、人々はそうしています。


また、共有メモリIPCを、メモリマップファイルなしで実装できるかどうかを暗黙的に尋ねました。

  • 実用的な方法は、メモリのみのファイルシステムにあるファイルのメモリマップファイルを作成することです。例えばLinuxの「tmpfs」。

    技術的には、これはまだメモリマップファイルです。ただし、データをディスクにフラッシュするオーバーヘッドが発生せず、プライベートIPCデータがディスク上に残るという潜在的なセキュリティの懸念を回避できます。

  • 理論上次のようにして、2つのプロセス間に共有セグメントを実装できます。

    • 親プロセスで、mmapを使用して、MAP_ANONYMOUS | MAP_SHAREDでセグメントを作成します。
    • 子プロセスをフォークします。これらは最終的に、すべてのセグメントを相互および親プロセスと共有します。

    ただし、それをJavaプロセスに実装するのは...困難です。 AFAIK、Javaはこれをサポートしていません。

参照:

0
Stephen C

3つのポイントに言及する価値があります:パフォーマンス、同時変更、およびメモリ使用率。

MMAPベースは通常、ファイルベースのIOよりもパフォーマンスが優れているという評価では、あなたは正しいです。特に、コードが多くの小さいIOをファイルの任意のポイントで実行する場合、パフォーマンスの利点は重要です。

n番目のバイトを変更することを検討してください:mmap buffer[N] = buffer[N] + 1、およびファイルベースのアクセスでは、(少なくとも)4つのシステムコール+エラーチェックが必要です。

   seek() + error check
   read() + error check
   update value
   seek() + error check
   write + error check

実際の数IO(ディスクに対して))の数は同じである可能性が高いです。

同時アクセスに注目する2番目のポイント。ファイルベースのIOでは、潜在的な同時アクセスを心配する必要があります。 2つのプロセスが同時に誤って値にアクセスすることを防ぐために、明示的なロック(読み取り前)およびロック解除(書き込み後)を発行する必要があります。共有メモリを使用すると、アトミック操作により、追加のロックの必要性を排除できます。

3番目のポイントは、実際のメモリ使用量です。共有オブジェクトのサイズが大きい場合、共有メモリを使用すると、追加のメモリを割り当てることなく、多数のプロセスがデータにアクセスできます。メモリに制約があるシステム、またはリアルタイムのパフォーマンスを提供する必要があるシステムの場合、これがデータにアクセスする唯一の方法である可能性があります。

0
dash-o