如何确定每次读取系统调用要读取的合理字节数?

How to determine reasonable number of bytes to read per read system call?

我正在使用文件 reading/writing,但很难决定为 "read" 系统调用设置多大的读取缓冲区。

特别是,我正在查看“http://pubs.opengroup.org/onlinepubs/009695399/functions/read.html

除了SSIZE_MAX之外,似乎没有对我一次可以读取多少字节有任何限制。

更糟糕的是,如果我创建一个包含 SSIZE_MAX 个字符的数组,程序会生成一个:

sh: ./codec: Bad file number

是否有任何合理的方法来决定每次读取系统调用要读取多少字节?我担心的是,这可能会因系统而异(我不能只进行尽可能多的读取,直到读取无法确定我可以读取的确切字节数,即使我这样做了,也不一定会更快比读取更少的字节)。

我的一个想法是检查我的 CPU 缓存大小并尝试使我的缓冲区不大于该大小,但由于我不知道 CPU 缓存是如何工作的,所以我不知道确定这是否一定正确。

提前致谢。

在您要阅读的文件上调用 stat()fstat()struct stat 成员 st_blksize 包含最佳缓冲区大小,您应该使用它来读取您调用 stat() 的文件。

我也思考过基本相同的问题,得出一个非常简单的结论:

使用保守的默认值或启发式算法,但如果用户愿意,可以轻松覆盖它。

您知道,在某些情况下,用户可能不希望您的实用程序达到最大吞吐量,但可能会在后台执行任何操作。也许任务并不那么重要。就个人而言,在 Linux 中,我经常使用 niceionice 实用程序将长期但不优先的任务放在次要位置,可以这么说,这样它们就不会干扰与我的实际工作。

过去十年内的基准表明 128k 到 2M 的块大小(217 到 221 字节)可以始终如一地工作——在几乎所有情况下都离最佳利率不远——平均水平缓慢地向该范围的较大端移动。通常情况下,2 的幂似乎比非 2 的幂更好,尽管我还没有看到足够多的各种 RAID 配置的基准来完全相信这一点。

因为几乎肯定会为每个新硬件重新编译您的实用程序 type/generation,我更希望有一个默认的块大小,在编译时定义,但在 运行 时将其简单地覆盖时间(通过命令行选项、环境变量、and/or 配置文件)。

如果您的实用程序是为当前的 POSIXy 操作系统打包的,则二进制文件可以使用似乎最适合在该机器上完成的任务类型的默认值;例如,Raspberry Pis 和其他 SBC 通常没有那么多内存可供启动,因此较小(例如 65536 字节)的默认块大小可能效果最好。桌面用户可能不关心内存消耗,因此您可以在当前桌面计算机上使用更大的默认块大小。

(服务器和高性能计算(这是我思考过的地方),块大小基本上是根据确切的硬件和工作负载进行基准测试,或者只是一个勉强的猜测。通常后者。)

或者,您可以根据所涉及文件的 st_blksize 构造一个启发式算法,也许乘以一个默认因子,并限制在某个首选范围内。然而,随着硬件的变化,这种启发式方法往往会很快变位。

使用启发式方法时,重要的是要记住这个想法并不总是达到最佳状态,而是要避免非常糟糕的结果。如果用户想要挤出最后百分之几的性能,他们可以在自己的工作流程中进行一些基准测试,并相应地调整默认值。 (我个人有,并且正在做。)

好吧,确定适当的缓冲区大小完全取决于问题。首先,我将检查确定缓冲区大小的最新技术:stdio 使用 BUFSZ 作为缓冲区大小(通常是一个 unix 磁盘块的大小,一旦固定为 512,现在可能在 1024 到 4096 之间 --- 从磁盘块大小到虚拟页面大小 ---)对于在这里移动的数量来说,这太低了,但这是一个很好(并且认为)可以接受的值。

另一方面,考虑一个只有 8Kb 内存并使用 1 兆字节缓冲存储的嵌入式系统。将虚拟内存用于缓冲存储(如果允许的话)听起来有些奇怪。

假设您正在设计一个文件复制实用程序,其中最佳缓冲区大小确定将是最佳的。可能认为最大的可接受值是必须的。但经过一些测试后,您会发现大量误用的内存。假设您将流程设计为仅使一个线程充当 reader 和写入器。您读取数据,然后将该数据写入另一个文件。出现的第一件事是您使用的内存量不会影响,因为这只会影响进程的写入和读取顺序......如果一次读取意味着一次磁盘读取(假设一次一个块)超出磁盘块大小的内容不会让您进行额外的读取以获取相同的数据(这实际上是在系统级别完成的,它会缓冲您的数据,从而可以在一个单字节块中读取数据,而系统正在逐块读取数据)

第二种方法是尽量减少系统调用。在这一点上,每个人都知道进行系统调用是一件代价高昂的事情,所以如果我们能够安排进行最少的系统调用,我们就会得到一些好处。但过了一会儿,你会发现其中没有额外的性能,因为系统正在逐块读取你的数据,而你的进程正在等待它,使得系统调用惩罚完全不可见,因为它代表不到 1%的等待时间。此外,系统必须保证您的数据在此期间不会更改,进行原子读取调用(这是通过从头到尾锁定文件的索引节点来完成的),因此没有进程实际上可以引用同一个文件直到你完成通话。允许较大的缓冲区会使您的进程可能太大而无法容纳在内存中,并通过额外的交换处理加载您的系统。这让你在进入大缓冲区之前要小心。

最后,额外的系统调用惩罚与使用大缓冲区所产生的惩罚相比微不足道,根本没有用。如果你生活在一个正常大小的系统中(假设笔记本电脑或台式电脑有 2-8Gb 的内存),8Kb 的缓冲区大小可能适用于所有情况。

最后要考虑的一件事是 audio/video 流缓冲区大小的确定。在这种情况下,通常有一个生产者(要复制的 reader 数据)以不同的速度生成数据(例如,随着网络负载的变化而变化)和一个以固定速率吃掉这些数据的消费者(让我们比如电信呼叫 8kbps,CD 播放 192kbps,等等)缓冲区大小必须允许补偿数据提供速度的变化而不是清空缓冲区,因为那样的话,我们将有一个无声期来填补空白.这本质上是动态的,您必须预先计算可能的网络数据包丢失和重传容限。通过延迟消费者并用流数据填充一些缓冲区,您可以补偿并让数据消费者满意而不会丢失数据。在这种情况下,2Mb 的流缓冲区在某些情况下是常识,具体取决于数据丢失的概率、重传时间和您想要承受的流媒体质量。