为什么fwrite libc函数比syscall write函数更快?

26

提供了同一个程序,它会读取一个随机生成的输入文件,将读取到的字符串回显到输出中。唯一的区别在于,在一侧提供了来自Linux系统调用的读写方法,而在另一侧则使用fread/fwrite。

对于大小为10Mb并将其回显到/dev/null,并确保文件不被缓存的输入进行应用程序定时,我发现当使用非常小的缓冲区(例如1字节)时,libc的fwrite速度比较快。

这里是使用fwrite的时间输出:

real    0m0.948s
user    0m0.780s
sys     0m0.012s

使用系统调用write:

real    0m8.607s
user    0m0.972s
sys     0m7.624s

我能想到的唯一可能性是,内部的libc已经缓存了我的输入...不幸的是,我在网上找不到太多有关此事的信息,所以也许这里的大师们可以帮助我解决问题。


4
"internally libc is already buffering my input" 的意思是“libc 内部已经对我的输入进行了缓存”。这正是它正在做的。如果你愿意,甚至可以阅读 libc 的源代码,看看它是如何实现缓存的。 - kquinn
3个回答

40
通过使用大小为10Mb的输入并将其回显到/dev/null,确保文件未缓存,我发现当使用非常小的缓冲区(例如1字节)时,libc的fwrite比write快得多。fwrite适用于流,这是有缓冲区的。因此,许多小缓冲区将更快,因为它不会运行昂贵的系统调用,直到缓冲区填满(或者您刷新它或关闭流)。另一方面,发送到写入的小缓冲区将运行昂贵的系统调用以每个缓冲区-这就是你失去速度的地方。使用1024字节流缓冲区,并编写1字节缓冲区,您将看到每个千字节1024个写入调用,而不是1024个fwrite调用变成一个写入-看到差异了吗?对于大缓冲区,差异将很小,因为缓冲区较少,因此在fwrite和write之间存在更一致的系统调用次数。
换句话说,fwrite(3)只是一个库例程,它将输出收集到块中,然后调用write(2)。而write(2)是一个系统调用,它陷入内核。那里才是I/O实际发生的地方。仅调用内核存在一些开销,然后是实际写入的时间。如果您使用大缓冲区,您会发现write(2)更快,因为它最终必须被调用,并且如果您每次写入一次或多次,则fwrite缓冲区开销只是更多的开销。如果您想了解更多相关信息,可以查看这份文档,它详细解释了标准I/O流。

15

write(2) 是基本的内核操作。

fwrite(3) 是在write(2)之上添加缓冲区的库函数。

对于小的(例如,逐行)字节计数,fwrite(3) 更快,因为仅需执行内核调用的开销较小。

对于大的(块I/O)字节计数,write(2) 更快,因为它不需要缓冲区,并且在两种情况下都需要调用内核。

如果查看cp(1)的源代码,您将看不到任何缓冲区。

最后,还有一个考虑因素:ISO C和Posix。像fwrite这样的缓冲库函数是在ISO C中指定的,而像write这样的内核调用是Posix的。尽管许多系统声称具有Posix兼容性,尤其是在试图获得政府合同资格时,但实际上它只适用于类Unix系统。因此,缓冲操作更具可移植性。因此,Linux的cp肯定会使用write,但必须跨平台运行的C程序可能必须使用fwrite。


1
我最近参加了一次面试,我给出了关于write和fwrite之间差异的相同论据,但是我得到的回复是:“你对这种差异的认识完全是错误的”!! 面试官对我来说似乎非常傲慢。尽管如此,我只是想确认一下,通过glibc进行的调用和直接向内核发出的调用之间是否有任何其他差异? - Piyush Kansal

11

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接