Unix系OSのIPCを比較|パイプ・共有メモリ・UNIXドメインソケットの用途と性能の違い

Unix系

Unix系OSでは、複数のプロセスがデータを受け渡したり処理を連携したりするために、さまざまなプロセス間通信(IPC:Inter-Process Communication)の仕組みが用意されています。代表的なのがパイプ、共有メモリ、UNIXドメインソケットです。

これらはどれも同じPC内のプロセス同士をつなぐために使えますが、データの流れ方、実装の複雑さ、双方向通信のしやすさ、コピー回数、同期方法、拡張性が大きく異なります。そのため、単純に「一番速いIPCを選べばよい」というものではありません。

この記事では、Unix系OSにおけるパイプ、共有メモリ、UNIXドメインソケットの内部的な考え方から、それぞれが向いている用途、性能上の特徴、選び方まで分かりやすく解説します。

Unix系OSのIPCとは何か

通常、プロセスはそれぞれ独立した仮想アドレス空間を持っています。あるプロセスの変数を、別のプロセスがそのまま直接読み書きすることはできません。

そこでOSが、データを渡すための仕組みとしてIPCを提供します。パイプやソケットではカーネルを介してデータを送受信し、共有メモリでは同じメモリ領域を複数プロセスから参照できるようにします。

つまりIPCの違いは、「データをカーネル経由でコピーするのか」「同じメモリを直接共有するのか」「通信相手をどのように識別するのか」という設計思想の違いでもあります。

パイプは単純なストリーム通信に向く

パイプはUnixで古くから使われているシンプルなIPCです。あるプロセスが書き込んだバイト列を、別のプロセスが順番に読み取ります。

シェルでよく使う「|」もパイプです。例えば次のコマンドでは、lsの出力がgrepの入力へそのまま渡されます。

ls -l | grep txt

このように、前段の処理結果を後段へ流す用途では非常に扱いやすく、Unixの「小さなツールを組み合わせる」という設計思想とも相性がよいIPCです。

無名パイプと名前付きパイプの違い

一般的なpipe()で作る無名パイプは、親子プロセスなど、ファイルディスクリプタを継承できる関係で使われることが多いです。fork()後の親子プロセス通信が典型例です。

一方、FIFOとも呼ばれる名前付きパイプはファイルシステム上に名前を持つため、直接親子関係のないプロセス同士でも利用できます。

例えばmkfifoでFIFOを作成すると、別々に起動したプログラムが同じFIFOを開いてデータをやり取りできます。ただし基本的な通信モデルはパイプと同じストリーム型です。

パイプは基本的に一方向通信と考えると分かりやすい

通常のパイプは、一方が書き込み、もう一方が読み込みを行う一方向通信として使います。双方向通信を行いたい場合は、2本のパイプを用意する必要があります。

例えば親プロセスから子プロセスへの命令用に1本、子から親への結果返却用にもう1本作る、といった構成です。

そのため、要求と応答を頻繁に繰り返すクライアント・サーバー型の通信では、UNIXドメインソケットのほうが設計しやすい場合があります。

共有メモリは大量データを高速に扱いやすい

共有メモリは、複数のプロセスから同じ物理メモリ領域を参照できるようにするIPCです。POSIX共有メモリ、System V共有メモリ、mmapを使った共有マッピングなどの方法があります。

共有メモリの大きな特徴は、一度マッピングした後は、プロセス同士が同じ領域を直接読み書きできることです。パイプやソケットのように、データを送るたびに通常のread/writeによるコピーを繰り返す必要がありません。

そのため、大容量データを高頻度で共有する用途では、共有メモリが最も高いスループットを出しやすいIPCの一つです。

共有メモリが速い理由

パイプやソケットでは、ユーザー空間のバッファからカーネル側のバッファへデータをコピーし、受信側で再びユーザー空間へ渡す処理が発生します。実装やOSの最適化によって詳細は異なりますが、少なくとも通信処理にはカーネルの介在があります。

一方、共有メモリでは両方のプロセスが同じメモリページを自分のアドレス空間へマッピングできます。送信側が共有領域へ書き込めば、受信側は同じ内容を直接読めます。

数MBから数GBの画像、映像フレーム、大規模配列などを頻繁に交換する場合、データそのものを毎回コピーしないことが大きな性能上のメリットになります。

共有メモリでは同期を自分で設計する必要がある

共有メモリは高速ですが、その代わり実装が難しくなります。複数プロセスが同じ領域へ同時にアクセスできるため、競合状態を防ぐ必要があるからです。

例えばプロセスAが構造体を書き換えている途中でプロセスBが読み取ると、半分だけ新しいデータという不整合な状態を見る可能性があります。

そこでPOSIXセマフォ、pthread mutexのプロセス共有設定、アトミック操作、イベント通知などを組み合わせて同期します。共有メモリでは、データ転送の速度と引き換えに同期設計の責任がアプリケーション側へ移ると考えると分かりやすいでしょう。

UNIXドメインソケットはローカルのクライアント・サーバー通信に強い

UNIXドメインソケットは、TCP/IPソケットと似たプログラミングモデルを使いながら、同じホスト内のプロセス間通信を行う仕組みです。

ネットワーク用のIPアドレスやポート番号ではなく、ファイルシステム上のパスやOS固有の名前空間を通信先として利用します。

例えばWebサーバーとアプリケーションサーバー、データベースとクライアント、Dockerデーモンと管理ツールなど、ローカルのサービス間通信で広く使われています。

UNIXドメインソケットは双方向通信が簡単

パイプと比較したUNIXドメインソケットの大きなメリットは、1つの接続で双方向通信を自然に行えることです。

クライアントが要求を送り、サーバーが応答を返すという一般的なRPCやAPI型の通信を作りやすく、複数クライアントからの接続も扱えます。

さらにsocket、bind、listen、accept、connectなど、TCPソケットと非常に似たAPIを利用できるため、将来的にネットワーク通信へ変更する可能性があるシステムでも設計を共通化しやすい利点があります。

UNIXドメインソケットはTCPより軽量になりやすい

同一ホスト内で通信する場合、UNIXドメインソケットは通常、TCP/IPのようなIPルーティング処理やネットワーク層の一部を必要としません。そのためlocalhostへのTCP接続よりオーバーヘッドが小さくなりやすいです。

ただし、共有メモリのように完全にデータコピーを避ける仕組みではありません。一般的にはカーネルのソケットバッファを介してデータを転送します。

そのため性能傾向としては、共有メモリほどピーク性能を追求しない代わりに、実装の容易さと十分な高速性を両立するIPCと考えることができます。

ストリーム型とデータグラム型を選べる

UNIXドメインソケットでは、SOCK_STREAMだけでなくSOCK_DGRAMやSOCK_SEQPACKETなどを利用できる環境があります。

SOCK_STREAMはTCPと同じような連続したバイトストリームとして扱います。一方SOCK_DGRAMではメッセージ単位を保持しやすくなります。

通信内容が「固定形式のメッセージを1件ずつ送る」タイプなら、メッセージ境界を保持できる方式が便利な場合があります。

ファイルディスクリプタを渡せるのもUNIXドメインソケットの特徴

Unix系OSのUNIXドメインソケットには、通常のバイト列だけでなく、補助データとしてファイルディスクリプタを別プロセスへ渡せる仕組みがあります。

例えば、あるプロセスが開いたファイルやソケットを、別のプロセスへ権限付きで引き渡すことができます。

これは特権プロセスと非特権プロセスを分離する設計や、サーバープロセス間で接続を引き渡す設計などで非常に便利です。単純なパイプにはないUNIXドメインソケット特有の強みの一つです。

3種類の性能を単純比較するとどうなる?

一般的な傾向として、データ転送量が大きく高頻度になるほど共有メモリが有利になりやすく、単純なストリーム処理ではパイプ、複雑な双方向通信ではUNIXドメインソケットが使いやすくなります。

項目 パイプ 共有メモリ UNIXドメインソケット
速度 高速 非常に高速になりやすい 高速
大量データ 中程度 非常に向く 中~大規模まで対応
双方向通信 2本必要 可能だが同期設計が必要 容易
複数クライアント 扱いにくい 自前設計が必要 扱いやすい
同期 カーネルがある程度管理 アプリ側で必要 カーネルが通信を管理
実装難易度 低い 高い 中程度
メッセージ境界 基本なし 自前設計 方式によって保持可能

ただし、実際の速度はメッセージサイズ、送信頻度、CPU、OS、同期方式、コピー回数などによって大きく変わります。「共有メモリなら必ず何倍速い」という固定値はありません。

小さいメッセージでは共有メモリが必ず有利とは限らない

共有メモリは大容量転送で強力ですが、数バイトや数十バイト程度の小さな通知を低頻度で送るだけなら、その高速性を活かせない場合があります。

共有メモリでは同期用のmutexやセマフォ、リングバッファなどを自分で設計する必要があり、その管理コストのほうが大きくなることもあります。

例えば「設定を再読み込みしてください」という短い通知だけなら、UNIXドメインソケットやパイプのほうが実装も簡単で十分高速です。

大容量データでは共有メモリ+通知IPCの組み合わせも有効

高性能なシステムでは、1種類のIPCだけを使うとは限りません。共有メモリと別の通知手段を組み合わせる設計もよく使われます。

例えば数百MBの画像データそのものは共有メモリへ置き、「新しいフレームを書き込みました」という通知だけをUNIXドメインソケットやeventfdなどで送ります。

この方式なら、大きなデータを何度もコピーせずに済み、同時に通知や同期も分かりやすく実装できます。

パイプが向いている具体例

パイプは、データが基本的に一方向へ順番に流れる処理に向いています。Unixシェルのコマンド連結はその代表例です。

例えばログ生成プロセスの出力を加工プロセスへ渡す、親プロセスが起動した子プロセスから結果を受け取る、といった用途です。

通信相手が固定され、複雑な接続管理が不要なら、パイプはコード量が少なく非常に扱いやすい選択肢です。

共有メモリが向いている具体例

共有メモリは、映像、画像、音声バッファ、科学計算の大型配列など、大きなデータを頻繁に複数プロセスで扱う用途に適しています。

例えばカメラ取得プロセスが毎秒60枚の4K画像を書き込み、画像解析プロセスがそれを読み取るシステムでは、毎フレームをソケット経由でコピーするより共有メモリが有利になる場合があります。

一方で、書き込み中のフレームを読み込まないようダブルバッファやリングバッファ、アトミックな状態管理などを設計する必要があります。

UNIXドメインソケットが向いている具体例

UNIXドメインソケットは、同一マシン上で動くサービス同士の要求・応答通信に特に向いています。

例えばGUIアプリとバックグラウンドデーモン、Webサーバーとアプリケーションサーバー、管理CLIと常駐サービスなどです。

複数のクライアントが同じサーバーへ接続し、それぞれ独立した要求を送るような構造では、パイプや共有メモリよりUNIXドメインソケットのほうが設計しやすいことが多いです。

セキュリティや権限管理にも違いがある

名前付きUNIXドメインソケットやFIFOはファイルシステム上のパスを利用するため、所有者やパーミッションによるアクセス制御を行いやすい特徴があります。

共有メモリについてもPOSIX共有メモリなどでは権限設定が可能ですが、共有領域へアクセスを許可した後は、メモリ内容を直接読み書きできるため設計を慎重に行う必要があります。

特権を分離するシステムでは、必要最小限の通信だけをUNIXドメインソケット経由で公開する設計が採用されることも多くあります。

性能だけでIPCを選ばないことが重要

IPC選択ではベンチマーク上の最大スループットだけでなく、障害時の扱いやすさ、デバッグ、保守性も重要です。

共有メモリで複雑なロック競合が起きると、再現性の低いバグやデッドロックにつながることがあります。一方、ソケット通信なら送受信ログを取りやすく、プロトコルとして分離しやすいという利点があります。

数%の性能差より、障害解析や将来の拡張が重要なシステムでは、少しオーバーヘッドがあってもUNIXドメインソケットを選ぶほうが合理的なことがあります。

まとめ|単純な流れはパイプ、大容量は共有メモリ、サービス間通信はUNIXドメインソケット

Unix系OSの代表的なIPCであるパイプ、共有メモリ、UNIXドメインソケットは、それぞれ得意分野が異なります。

パイプは単純な一方向ストリームに向き、共有メモリは大量データを低コピーで高速に扱う用途に強く、UNIXドメインソケットは双方向・複数クライアント型のローカルサービス通信に向いています。

共有メモリは理論上の性能を高めやすい一方で同期処理が複雑になり、UNIXドメインソケットは多少のカーネル処理を伴う代わりに通信モデルが分かりやすくなります。IPCを選ぶときは、データ量、通信方向、接続相手の数、同期の難しさ、将来の拡張性まで含めて判断することが重要です。

コメント

タイトルとURLをコピーしました