1. 概要

複数のプロセスが連携している場合、それらは通信する必要があります。 これは、プロセス間通信(IPC)と呼ばれます。 Linuxには、パイプやソケットなど、いくつかのIPCメソッドがあります。

この記事では、さまざまなIPCメソッドのパフォーマンスを分析します。 無名パイプ名前付きパイプ UNIXソケット、およびTCPソケットの速度を比較します。 各メソッドのベンチマークには、socatコマンドを使用します。

結果に直接ジャンプする場合は、セクション8「結果の比較」を確認してください。

2. はじめにと方法論

各IPC方式には、長所と短所があります。 たとえば、異なるコンピューターで実行されている2つのプロセス間で通信する場合、無名パイプを使用することはできません。 また、各IPCメソッドの動作は異なり、一部のメソッドは他のメソッドよりも高速になります。 たとえば、TCPソケットを使用する場合、システムはTCPヘッダーをペイロードに追加するため、オーバーヘッドがいくらか追加されます。

各IPCメソッドの速度を計算するために、テストを数回繰り返し、各反復の速度を計算するスクリプトを作成します。 次に、これらの結果を使用して、各メソッドの平均速度を計算します。

結果を比較したいので、各メソッドで同じソフトウェアとパラメーターを使用する必要があり、IPCメソッドのみを異なるようにする必要があります。 したがって、すべてのテストでsocatコマンドを使用します。 このコマンドを使用すると、ファイル、パイプ、およびソケットの読み取りと書き込みを行うことができます。 また、常に同じバイト数と同じコンテンツを転送します。

最後に、使用するブロックサイズも考慮する必要があります。 ブロックサイズは、一度に転送されるデータの量を決定し、大きなデータブロックの送信は小さなデータブロックの送信よりも高速であると予想されます。 そのため、さまざまなブロックサイズを使用して各メソッドの速度を測定します。

この記事では、次のパラメーターを使用します。

  • 入力ファイル: / dev / zero
  • 転送サイズ:2GB
  • ブロックサイズ:100バイト、500バイト、10KB、および1MB

特定のユースケースをテストする必要がある場合は、ニーズに合わせてパラメーターを調整できます。

3. パフォーマンスをベンチマークするためのスクリプトテンプレート

次のセクションでは、Bashスクリプトを使用してIPCメソッドのベンチマークを行います。 したがって、スクリプトテンプレートを使用して、同様のスクリプトですべてのIPCメソッドをテストします。

3.1. スクリプトテンプレート

パフォーマンスを計算するには、各メソッドのビットレートを計算する必要があります。したがって、テストごとに、転送サイズと転送の完了にかかった時間を知る必要があります。 次に、転送サイズを転送時間で割ってビットレートを取得できます。

また、異なるブロックサイズで各メソッドをテストするため、ブロックサイズを反復処理する必要があります。 また、ブロックサイズごとに、同じテストを数回実行して、各メソッドの平均速度を計算する必要があります。

パフォーマンスを計算するためのスクリプトテンプレートを作成しましょう

#!/bin/bash

BYTES=2000000000
BITS=$(($BYTES*8))
BSS=(100 500 10000 1000000)
IF=/dev/zero
OF=/dev/null
N=50
CSV=output.csv

echo "N,BS,BITS/S" > $CSV
for BS in ${BSS[@]}; do
    for i in `seq 1 $N`; do
        START=$(date +%s%N)

        ## Here goes the command that we need to benchmark ##

        END=$(date +%s%N)
        DURATION=$((($END - $START) / 1000000))
        BITRATE=$(($BITS * 1000 / $DURATION))
        echo $i,$BS,$BITRATE >> $CSV
    done
done

そこで、測定したいコマンドを書くことができ、スクリプトがその速度を計算します。 コメント付きの空白行が残っていることがわかります。

3.2. スクリプトテンプレートを理解する

スクリプトは、パフォーマンスのベンチマークに使用する変数の初期化から始まります。 そこで、ニーズに合わせて変数を変更できます。 たとえば、 N 変数を変更して反復回数を増減したり、BYTES変数を変更して転送サイズを変更したりできます。

初期化後、スクリプトはテストするブロックサイズを繰り返します。 次に、ブロックサイズごとに、同じテストN回を繰り返します。

Bash演算は、転送期間の計算に整数のみをサポートすることを覚えておく必要があります。 したがって、転送サイズに1,000を掛け、その結果をミリ秒単位の転送期間で除算します。 これにより、ビットレートがビット/秒で表示されます。

最後に、スクリプトが結果をCSV変数で指定されたファイルに書き込むことがわかります。 CSVファイルには、反復回数、ブロックサイズ、およびビットレート(ビット/秒)が含まれます。

3.3. 平均速度の計算

テストを実行した後、CSVファイルを解析して平均速度を計算できます。 CSVファイルを読み取り、各ブロックサイズの平均ビットレートを出力するBash関数を作成しましょう

$ calculate_averages() {
    tail -n+2 "$1" | awk -F, '{
        bitrate[$2] += $3;
        count[$2]++;
    }
    END {
        for (bs in count) {
            printf "Block size %s: %f Mbits/s\n", bs, bitrate[bs] / count[bs] / 1000000;
        }
    }'
}

calculate_averages 関数を呼び出すときは、ファイル名をパラメーターとして指定する必要があります。 この関数は、tailawkを使用して平均を計算します。 tail コマンドを使用して、各列の名前を含むCSVの最初の行をスキップします。 次に、awkの配列を使用して平均値を計算します。 この場合、浮動小数点演算を使用できるため、1,000,000で除算して、メガビット/秒で結果を取得します。

4. 匿名パイプ

匿名パイプは通常、コマンドラインで使用されます。 | 文字を使用して、Bashの2つのプロセス間で通信するのは簡単です。

4.1. テスト

したがって、 2つのプロセスを起動することで無名パイプの速度を測定できます。1つのプロセスはパイプに接続された標準出力に書き込み、もう1つのプロセスは同じパイプに接続された標準入力から読み取る必要があります。

これは、socatコマンドを使用して実行できます。 2つのsocatプロセスを起動して、一方から他方に2GBのデータを転送する方法を見てみましょう。

$ socat -b 1000000 OPEN:/dev/zero,readbytes=2000000000 STDOUT | socat -b 1000000 STDIN OPEN:/dev/null

その行を実行すると、両方のsocatコマンドが無名パイプを使用して通信します。 最初のsocatは1MByteのブロックサイズを使用し、 / dev / zeroから2GBを読み取り、それを標準出力に書き込みます。 同時に、2番目の socat は、1 MByteのブロックサイズを使用して標準入力から読み取り、 / dev /nullに書き込みます。

これで、前のセクションのスクリプトテンプレートを使用して、socat行を記述できます。 また、パラメーターをハードコーディングする代わりに変数を使用できます。

スクリプトテンプレートを使用してanonpipe.shというスクリプトを記述し、匿名パイプのベンチマークを作成しましょう

#!/bin/bash

BYTES=2000000000
BITS=$(($BYTES*8))
BSS=(100 500 10000 1000000)
IF=/dev/zero
OF=/dev/null
N=50
CSV=anonpipe.csv

echo "N,BS,BITS/S" > $CSV
for BS in ${BSS[@]}; do
    for i in `seq 1 $N`; do
        START=$(date +%s%N)
        socat -b $BS OPEN:$IF,readbytes=$BYTES STDOUT | socat -b $BS STDIN OPEN:$OF
        END=$(date +%s%N)
        DURATION=$((($END - $START) / 1000000))
        BITRATE=$(($BITS * 1000 / $DURATION))
        echo $i,$BS,$BITRATE >> $CSV
    done
done

ご覧のとおり、CSV変数をanonpipe.csvに変更しました。 これで、 ./ anonpipe.sh を実行して、前のスクリプトを実行できます。 また、ベンチマークを完了するには数分かかることを考慮する必要があります。

4.2. 結果

スクリプトが終了したら、前のセクションの関数calculate_averagesをanonpipe.csvファイルで呼び出しましょう。

$ ./anonpipe.sh
$ calculate_averages anonpipe.csv
Block size 100: 278.062607 Mbits/s
Block size 500: 1270.474921 Mbits/s
Block size 10000: 8070.641040 Mbits/s
Block size 1000000: 9039.146532 Mbits/s

100バイトのブロックサイズを使用した場合の最低のパフォーマンスは278Mbits/ sであり、1Mbyteのブロックサイズを使用した場合の最高のパフォーマンスは9,039Mbits/sでした。

5. 名前付きパイプ

名前付きパイプは、匿名パイプの代わりになります。 mkfifo コマンドを使用して名前付きパイプを作成すると、パイプとして動作する特別なファイルが作成されます。 ただし、パイプが存在しない場合は、socatコマンドを使用してパイプを作成します。

5.1. テスト

匿名パイプの例と同様に、 2つのプロセスを並行して実行することで、名前付きパイプのベンチマークを実行できます。 1つのプロセスは、入力から書き込み、名前付きパイプに書き込む必要があります。 他のプロセスは、名前付きパイプから読み取る必要があります。 これは、 socat コマンドとパイプ: パラメータ。 それをどのように行うことができるか見てみましょう:

$ socat -b 1000000 PIPE:namedpipe,rdonly OPEN:/dev/null &
$ socat -b 1000000 OPEN:/dev/zero,readbytes=2000000000 PIPE:namedpipe,wronly

まず、バックグラウンドで socat コマンドを実行します。このコマンドは、 namedpipe という名前付きパイプから読み取り、 / dev /nullに書き込みます。 最初のsocatプロセスが名前付きパイプからのデータを待機している間に、 / dev / zeroから2Gバイトを読み取り、それをに書き込む別のsocatコマンドを実行します。名前付きパイプ。 また、それに応じて、各パイプを読み取り専用または書き込み専用として構成します。

スクリプトテンプレートを使用してnamedpipe.shというスクリプトを作成し、名前付きパイプのベンチマークを作成します。

#!/bin/bash

BYTES=2000000000
BSS=(100 500 10000 1000000)
IF=/dev/zero
OF=/dev/null
N=50
CSV=namedpipe.csv
PIPENAME=namedpipe

echo "N,BS,BITS/S" > $CSV
for BS in ${BSS[@]}; do
    for i in `seq 1 $N`; do
        socat -b $BS PIPE:$PIPENAME,rdonly OPEN:$OF &
        START=$(date +%s%N)
        socat -b $BS OPEN:$IF,readbytes=$BYTES PIPE:$PIPENAME,wronly
        wait
        END=$(date +%s%N)
        DURATION=$((($END - $START) / 1000000))
        BITS=$(($BYTES*8))
        BITRATE=$(($BITS * 1000 / $DURATION))
        echo $i,$BS,$BITRATE >> $CSV
    done
done

この場合、CSV変数をnamedpipe.csvに変更しました。 また、最初のsocatコマンドを実行してから時間の測定を開始します。 これは、2番目のsocatコマンドを実行するまでデータを送信しないためです。 2番目のsocatが終了した後、 wait を使用して、最初のsocatコマンドが終了するのを待ちます。 これは、すべてのデータが1つのsocatから別のsocatに転送および受信されたことを確認するために行います。

これで、スクリプトを実行して./namedpipe.sh を実行している名前付きパイプをテストし、終了するまで待つことができます。

5.2. 結果

スクリプトが終了したら、namedpipe.csvファイルを使用して関数calculate_averagesを呼び出すことができます。 結果を見てみましょう:

$ ./namedpipe.sh
$ calculate_averages namedpipe.csv
Block size 100: 318.413648 Mbits/s
Block size 500: 1475.198028 Mbits/s
Block size 10000: 8843.554059 Mbits/s
Block size 1000000: 9699.212714 Mbits/s

名前付きパイプは、100バイトのブロックサイズを使用した場合の最低速度が318 Mbits / sであり、1Mバイトのブロックサイズを使用した場合の最高速度は9,699Mbits/sでした。

6. UNIXソケット

UNIXソケットを使用して2つのローカルプロセス間で通信できます。 このソケットタイプでは、IPアドレスとポートではなくパスにバインドします。

6.1. テスト

UNIXソケットのベンチマークを行うために、 2つのプロセスを並行して実行できます。1つはソケットへの書き込みで、もう1つはソケットからの読み取りです socat コマンドには、UNIXソケットを使用するための2つのパラメーターがあります。 UNIX-LISTEN:パラメーターを使用してUNIXソケットをリッスンできます。 次に、 UNIX:パラメーターを使用してUNIXソケットに接続できます。

socketと呼ばれるUNIXソケットによって接続されている2つのsocatコマンドを並列に実行してみましょう。

$ socat -b 1000000 UNIX-LISTEN:socket OPEN:/dev/null &
$ socat -b 1000000 OPEN:/dev/zero,readbytes=2000000000 UNIX:socket

最初のsocatコマンドはバックグラウンドで実行され、 socketと呼ばれるUNIXソケットをリッスンします。次に、2番目の socat コマンドはソケットに接続し、2Gバイトの / dev /zeroからのデータ。

スクリプトテンプレートを使用して、 unixsocket.shというスクリプトを作成し、UNIXソケットのベンチマークを行いましょう。

#!/bin/bash

BYTES=2000000000
BSS=(100 500 10000 1000000)
IF=/dev/zero
OF=/dev/null
N=50
CSV=unixsocket.csv
SOCKET=socket

echo "N,BS,BITS/S" > $CSV
for BS in ${BSS[@]}; do
    for i in `seq 1 $N`; do
        socat -b $BS UNIX-LISTEN:$SOCKET OPEN:$OF &
        START=$(date +%s%N)
        socat -b $BS OPEN:$IF,readbytes=$BYTES UNIX:$SOCKET
        wait
        END=$(date +%s%N)
        DURATION=$((($END - $START) / 1000000))
        BITS=$(($BYTES*8))
        BITRATE=$(($BITS * 1000 / $DURATION))
        echo $i,$BS,$BITRATE >> $CSV
    done
done

名前付きパイプスクリプトと同様のアイデアに従っていることがわかります。 最初のsocatを実行してから時間を測定し始めます。 また、2番目のコマンドの実行が終了した後、waitを使用します。 この場合、CSV変数を次のように設定します unixsocket.csv。 これで、次を実行してUNIXソケットをテストできます。 ./unixsocket.sh 、そしてそれが終了するまで待ちます。

6.2. 結果

スクリプトが終了したら、unixsocket.csvファイルを使用して関数calculate_averagesを呼び出すことができます。 結果を見てみましょう:

$ ./unixsocket.sh
$ calculate_averages unixsocket.csv
Block size 100: 245.992742 Mbits/s
Block size 500: 1184.959553 Mbits/s
Block size 10000: 15885.902502 Mbits/s
Block size 1000000: 41334.862565 Mbits/s

この場合、ブロックサイズが100バイトのときの最小ビットレートは245 Mbits / sで、ブロックサイズが1Mbyteのときの最大ビットレートは41,334Mbits/sでした。

7. TCPソケット

2つのプロセス間で通信する別の方法は、TCPソケットを使用することです。 実際、インターネットを介して2つのプロセス間で通信するように設計されたいくつかのプロトコルは、TCPソケットを使用します。 TCPソケットを使用して、同じコンピューター上の2つのプロセス間で通信することもできます。

7.1. テスト

UNIXソケットスクリプトから同じ考え方に従うことができますが、この場合はソケットタイプをTCPに変更する必要があります。したがって、[X146X]TCP-でsocatを使用する必要があります。 LISTEN:パラメーターはポートをリッスンし、 TCP:パラメーターはポートに接続します。 代わりにTCPを使用して、UNIXソケットテストを変更してみましょう。

$ socat -b 1000000 TCP-LISTEN:9999 OPEN:/dev/null &
$ socat -b 1000000 OPEN:/dev/zero,readbytes=2000000000 TCP:127.0.0.1:9999

この場合、最初の socat コマンドはポート番号9999をリッスンし、2番目のsocatコマンドはローカルホストIP127.0.0.1のポート9999に接続します。

スクリプトテンプレートを使用して、TCPソケットのベンチマークを行うためにtcpsocket.shというスクリプトを作成しましょう

#!/bin/bash

BYTES=2000000000
BSS=(100 500 10000 1000000)
IF=/dev/zero
OF=/dev/null
N=50
CSV=tcpsocket.csv
PORT=9999
IP=127.0.0.1

echo "N,BS,BITS/S" > $CSV
for BS in ${BSS[@]}; do
    for i in `seq 1 $N`; do
        socat -b $BS TCP-LISTEN:$PORT,reuseaddr OPEN:$OF &
        START=$(date +%s%N)
        socat -b $BS OPEN:$IF,readbytes=$BYTES TCP:$IP:$PORT
        wait
        END=$(date +%s%N)
        DURATION=$((($END - $START) / 1000000))
        BITS=$(($BYTES*8))
        BITRATE=$(($BITS * 1000 / $DURATION))
        echo $i,$BS,$BITRATE >> $CSV
    done
done

この場合、CSV変数はtcpsocket.csvに設定されます。 これで、スクリプト ./ tcpsocket.sh を実行してTCPソケットのベンチマークを行い、完了するまで待つことができます。

7.2. 結果

スクリプトが終了したら、tcpsocket.csvファイルを使用して関数calculate_averagesを呼び出すことができます。結果を見てみましょう。

$ ./tcpsocket.sh
$ calculate_averages tcpsocket.csv
Block size 100: 269.562354 Mbits/s
Block size 500: 1284.184400 Mbits/s
Block size 10000: 14798.750616 Mbits/s
Block size 1000000: 36208.454080 Mbits/s

この最後の方法では、100バイトのブロックサイズを使用したときに最低のパフォーマンスが269 Mbits/sであったことがわかります。 また、1 Mバイトのブロックサイズを使用した場合、最速の速度は36,208 Mbits/sでした。

8. 結果の比較

最後に、各メソッドのパフォーマンスを測定した後、それらを比較できます。 パフォーマンスを視覚化できるように、前のセクションの結果をグラフにプロットしてみましょう

ご覧のとおり、グラフには、X軸にテストした4つのIPCメソッドと、Y軸にMbits/s単位の速度が示されています。 また、Y軸に対数目盛を使用しました

チャートを分析した後、各メソッドのパフォーマンスがブロックサイズに依存することに気付くことができます。 100バイトと500バイトの最小ブロックサイズを使用すると、パイプはソケットよりもわずかに高速であることがわかります。 ただし、最大のブロックサイズである10Kバイトと1Mバイトを使用した場合、ソケットはパイプよりも高速でした。

100バイトのブロックサイズで最も速いIPCメソッドは名前付きパイプで、最も遅いのはUNIXソケットでした。名前付きパイプは318 Mbits/sの速度で送信されました。 245 Mbits/sで送信されるUNIXソケット。 したがって、相対的な用語では、名前付きパイプは、ブロックサイズが100バイトのUNIXソケットよりも約30% fアスターです。

最大のブロックサイズである1Mバイトを比較すると、最も速いIPC方式はUNIXソケットであり、最も遅いのは無名パイプでした。 UNIXソケットは41,334Mbits/ sの速度で送信され、無名パイプは9,039 Mbits/sの速度で送信されました。 したがって、UNIXソケットは、1Mバイトのブロックサイズを使用する場合、無名パイプよりも約350% fアスターです。

この情報から、使用するIPC方式を決定する前に、送信するメッセージのサイズを検討する必要があると結論付けることができます。 小さなメッセージを送信する場合、名前付きパイプが最速です。 ただし、大きなメッセージを送信する場合は、UNIXソケットが最速です。

9. 結論

この記事では、4つのIPCメソッドのパフォーマンスを分析しました。 名前付きパイプ、名前付きパイプ、UNIXソケット、TCPソケットを比較しました。

小さなブロックサイズを使用し、パイプに名前付きパイプを使用した場合、パイプはソケットよりも高速であることがわかりました。 ただし、ブロックサイズが大きい場合、ソケットのパフォーマンスはパイプよりも著しく優れており、UNIXソケットが最速でした。