IPC性能比较:匿名管道、命名管道、Unix套接字和TCP套接字
1. 概述
当我们有多个进程一起工作时,它们可能需要通信。这称为进程间通信 (IPC)。在 Linux 中,有多种 IPC 方法,如管道和套接字。
在本文中,我们将分析不同 IPC 方法的性能。我们将比较匿名管道 、命名管道 、UNIX 套接字 和 TCP 套接字的速度。为了对每种方法进行基准测试,我们将使用socat 命令。
如果您想直接跳到结果中,请查看第 8 节“比较结果”。
2. 介绍和方法
每种 IPC 方法都有其优点和缺点。例如,如果我们想在不同计算机上运行的两个进程之间进行通信,我们就不能使用匿名管道。此外,每种 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 文件来计算平均速度。让我们编写一个 Bash 函数来读取 CSV 文件并打印每个块大小的平均比特率:
$ 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函数时,我们必须将文件名作为参数。该函数使用tail和awk计算平均值。使用tail 命令,我们跳过 CSV 中包含每列名称的第一行。然后,我们使用awk 的数组来计算平均值。在这种情况下,我们可以使用浮点运算,因此我们除以 1,000,000 以获得每秒兆位的结果。
4. 匿名管道
匿名管道通常用于命令行。*使用|*在 Bash 中的两个进程之间进行通信很简单。特点。
4.1. 测试
因此,我们可以通过启动两个进程来测量匿名管道的速度:一个进程应该写入连接到管道的标准输出,另一个进程应该从连接到同一管道的标准输入读取。
我们可以使用socat命令来做到这一点。让我们看看如何启动两个socat进程以将 2 GB 的数据从一个进程传输到另一个进程:
$ socat -b 1000000 OPEN:/dev/zero,readbytes=2000000000 STDOUT | socat -b 1000000 STDIN OPEN:/dev/null
当我们运行该行时,两个socat命令都使用匿名管道进行通信。第一个socat使用 1 MByte 块大小,从*/dev/zero读取 2 GB ,并将其写入标准输出。同时,第二个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. 结果
脚本完成后,让我们使用anonpipe.csv文件调用上一节中的函数calculate_averages :
$ ./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 字节块大小时,最低性能为 278 Mbits/s,而当我们使用 1 Mbyte 块大小时,最高性能为 9,039 Mbits/s。
5. 命名管道
命名管道是匿名管道的替代品。我们可以使用*mkfifo * 命令创建一个命名管道,它会创建一个特殊的文件来充当管道。但是,如果管道不存在,我们将使用socat命令创建管道。
5.1. 测试
与匿名管道示例类似,**我们可以通过并行运行两个进程来对命名管道进行基准测试。一个进程应该从输入写入并写入命名管道。另一个进程应该从命名管道中读取。**我们可以使用socat命令和*PIPE:*参数来做到这一点。让我们看看我们是如何做到的:
$ 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进程等待来自命名管道的数据时,我们运行另一个socat命令,从**/dev/zero读取 2 GB并将其写入命名管道。此外,我们相应地将每个管道配置为只读或只写。
让我们使用脚本模板编写一个名为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命令后才开始测量时间。这是因为在我们运行第二个socat命令之前,我们不会传输任何数据。在第二个socat完成后,我们使用wait 等待第一个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,而在使用 1 Mbyte 块大小时其最高速度为 9,699 Mbits/s。
6. UNIX 套接字
我们可以使用 UNIX 套接字在两个本地进程之间进行通信。使用这种套接字类型,我们绑定到路径而不是 IP 地址和端口。
6.1. 测试
为了对 UNIX 套接字进行基准测试,我们可以并行运行两个进程,一个写入套接字,另一个从套接字读取。socat命令有两个参数来使用UNIX 套接字。我们可以使用*UNIX-LISTEN:参数来监听 UNIX 套接字。然后,我们可以使用UNIX:*参数连接到 UNIX 套接字。
让我们并行运行两个通过名为socket的 UNIX 套接字连接的socat命令:
$ socat -b 1000000 UNIX-LISTEN:socket OPEN:/dev/null &
$ socat -b 1000000 OPEN:/dev/zero,readbytes=2000000000 UNIX:socket
第一个socat命令在后台运行并监听名为socket 的 UNIX 套接字。然后,第二个socat命令连接到套接字并从*/dev/zero*传输 2 GB 的数据。
让我们使用脚本模板并编写一个名为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后开始测量时间。此外,我们在第二个命令完成执行后使用wait。在这种情况下,我们将 CSV 变量设置为unixsocket.csv。我们现在可以通过运行*./unixsocket.sh*来测试 UNIX 套接字,然后等待它完成。
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,而当块大小为 1 Mbyte 时,最大比特率为 41,334 Mbits/s。
7. TCP 套接字
在两个进程之间进行通信的另一种方法是使用 TCP 套接字。事实上,一些旨在通过 Internet 在两个进程之间进行通信的协议使用 TCP 套接字。我们还可以使用 TCP 套接字在同一台计算机上的两个进程之间进行通信。
7.1. 测试
**我们可以从 UNIX 套接字脚本中遵循相同的想法,但是在这种情况下我们应该将套接字类型更改为 TCP。**所以,我们应该使用socat和*TCP-LISTEN:参数来监听一个端口和TCP:*参数来连接一个端口。让我们更改 UNIX 套接字测试,改用 TCP:
$ 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,第二个socat命令连接到 localhost IP 127.0.0.1 上的端口 9999。
让我们使用脚本模板并编写一个名为tcpsocket.sh的脚本来对 TCP 套接字进行基准测试:
#!/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 Mbyte 块大小时,最快的速度是 36,208 Mbits/s。
8. 比较结果
最后,在测量了每种方法的性能之后,我们现在可以对它们进行比较。让我们在图表上绘制前面部分的结果,以便我们可以可视化性能:
如我们所见,图表在 X 轴上显示了我们测试的四种 IPC 方法,在 Y 轴上显示了以 Mbits/s 为单位的速度。此外,我们在 Y 轴上使用了对数刻度。
分析图表后,我们可以注意到每种方法的性能取决于块大小。我们可以看到,当我们使用 100 字节和 500 字节的最小块大小时,管道比套接字稍快。但是,当我们使用 10 KB 和 1 MB 的最大块大小时,套接字比管道快。
100 字节块大小的最快 IPC 方法是命名管道,最慢的是 UNIX 套接字**。**命名管道以 318 Mbits/s 的速度传输,而 UNIX 套接字以 245 Mbits/s 的速度传输。因此,相对而言,命名管道比块大小为 100 字节的 UNIX 套接字快大约 30%。
当我们比较 1 Mbyte 的最大块大小时,最快的 IPC 方法是 UNIX 套接字,最慢的是匿名管道。UNIX 套接字以 41,334 Mbits/s 的速度传输,而匿名管道以 9,039 Mbits/s 的速度传输。因此,当使用 1 MB 块大小时,UNIX 套接字比匿名管道快大约 350%。
有了这些信息,我们可以得出结论,在决定使用哪种 IPC 方法之前,我们应该考虑要传输的消息的大小。如果我们发送小消息,命名管道是最快的。但是,如果我们发送大消息,UNIX 套接字是最快的。