为什么 macOS 上的 FIFO 管道比匿名管道慢约 8 倍?

问题描述 投票:0回答:1

在 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
)

c macos pipe posix named-pipes
1个回答
0
投票

在 macOS (14.4.1) 中,fifo 管道最终是 AF_UNIX 套接字,其限制由 https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/kern/uipc_proto.c# 决定L84-L92https://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 标志。作为解决方法,您可以引入一些分叉,并在进程中创建管道对以在读取器和写入器之间共享,但这可能过于人为。

备注:

  • XNU 代码对我来说并不容易阅读,我对正在创建哪种 vnode 文件以及调用哪些函数的假设可能是错误的,但迄今为止的经验数据支持我的理论。
  • 我也不确定为什么当编写器仍有数据要推入缓冲区时,
    soreceive
    会立即返回,但这可能与锁定数据结构以及可能的定时/多线程有关。
© www.soinside.com 2019 - 2024. All rights reserved.