在 M1 Max 上,我使用
mkfifo
创建了一个 FIFO 命名管道,并使用简单的 C 程序和 pv
测试写入/读取性能。该程序一次向 stdout 写入 65536 个字节。当执行 ./writer | pv > /dev/null
时,我得到了 ~8 GiB/s。当执行 ./writer >> mypipe
和 pv mypipe > /dev/null
时,我得到了 ~1 GiB/s。对于这两者,如果我打印执行的写入量,则两者之间的 8 倍系数大致相同。我还没有在 Linux 上测试过这个,还没有发现任何可以在 macOS/darwin 上运行的 fcntl
可以更改 FIFO 管道的缓冲区大小。
我想知道的是:
这是C程序:
#include <unistd.h>
#include <string.h>
#include <stdio.h>
int main() {
const int size = 65536;
char buf[size];
memset(buf, 0, size);
while (1) {
if (write(1, buf, size) != size) {
fprintf(stderr, "bad\n");
}
}
return 0;
}
我已经验证在匿名管道被阻塞之前我可以写入的最多内容是 65536 (
M=0; while printf A; do >&2 printf "\r$((++M)) B"; done | sleep 999
)
在 macOS (14.4.1) 中,fifo 管道最终是 AF_UNIX 套接字,其限制由 https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/kern/uipc_proto.c# 决定L84-L92 和 https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/kern/uipc_usrreq.c#L920-L933 在撰写本文时,缓冲区大小流式套接字 (SOCK_STREAM) 的接收和发送是
#define PIPSIZ 8192
。匿名管道不是同一个概念,从测试来看,它们的缓冲区大小至少为 65536,即 8x PIPSIZ
。
从编写器端运行 dtrace 来看,似乎每个
write
系统调用都返回 65536
,但我相信是因为读取缓冲区限制为 8192。所以我们最终将付出读取器端上下文切换的成本从内核中的read
获取8192字节,返回给用户,再次调用read
。因此,虽然写入端在内核中写入这 65536 字节(请注意,这为我们节省了一些上下文切换,所以这很好),但读取端别无选择,只能不断来回切换。
关于如何增加这个数字或更改一些标志,我一直无法找出如何实现,因为 uipc 调用的 soreceive 方法似乎不接受任何用户设置的 IO 标志。作为解决方法,您可以引入一些分叉,并在进程中创建管道对以在读取器和写入器之间共享,但这可能过于人为。
备注:
soreceive
会立即返回,但这可能与锁定数据结构以及可能的定时/多线程有关。