好吧,尝试克服stdio.h 包中已经使用的缓冲系统几乎没有成功。如果您尝试将fwrite() 与更大的缓冲区一起使用,您可能不会赢得更多时间,并且会使用比必要更多的内存,因为stdio.h 会选择适合要写入数据的文件系统的最佳缓冲区大小。
如下所示的简单程序将表明速度无关紧要,因为 stdio 已经在缓冲输出。
#include <stdio.h>
int
main()
{
int c;
while((c = getchar()) >= 0)
putchar(c);
}
如果您尝试以上和以下程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main()
{
char buffer[512];
int n;
while((n = read(0, buffer, sizeof buffer)) > 0)
write(1, buffer, n);
if (n < 0) {
perror("read");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
您会发现没有显着差异,甚至第一个程序会更快,尽管它是基于每个字符执行 I/O。 (正如 B. Kernighan 和 Dennis Ritchie 在她的第一版“C 编程语言”中所写的那样)第一个程序很可能会获胜。
对read() 和write() 的调用各涉及一个系统调用,缓冲区大小由您决定。个人getchar() 和putchar() 电话不会。它们只是将接收到的字符存储在内存缓冲区中,当您打印它们时,其大小由stdio.h 库实现决定,基于文件系统,一旦缓冲区充满数据,它就会刷新缓冲区。如果您在第二个程序中增加缓冲区大小,您会看到将其增加到一个点会得到更好的结果,但之后您将看不到速度的增加。相对于执行实际 I/O 所涉及的时间而言,对库的调用次数是微不足道的,并且选择一个非常大的缓冲区会占用您系统的大量内存(并且 Raspberry Pi 内存在这个意义上是有限的,到 1Gb 或 ram)如果您因缓冲区太大而结束交换,您将彻底输掉这场战斗。
大多数文件系统都有一个首选的缓冲区大小,因为内核确实提前写入(在顺序读取时,内核读取的内容比您要求的要多,前提是您在使用数据后会继续阅读更多内容),这会影响最佳缓冲区大小。为此,stat(2) 系统调用会告诉您最佳缓冲区大小是多少,stdio 在选择实际缓冲区大小时会使用它。
不要认为你会比上面列出的程序更好(或更好)。即使您使用了足够大的缓冲区。
不正确(或有效)的是将缓冲调用(如所有 stdio 包)与基本系统调用(如 read(2) 或 write(2) ——正如我所看到的那样推荐你使用fflush(3) 在write(2) 之后,这是完全不连贯的——不缓冲数据)没有收益(如果你使用printf(3) 和部分进行部分调用,你可能会得到错误的输出排序使用write(2)(这种情况更多地发生在您计划做的管道中,因为缓冲区不是面向行的 --- stdio 中缓冲输出的另一个特征---)
最后,我建议您阅读 Dennis Ritchie 和 Rob Pike 的“Unix 编程环境”。它会教你很多关于unix的知识,但一件非常好的事情是它会教你完美地使用stdio包和unix文件系统对读写的调用。运气好的话,您会在互联网上以 .pdf 格式找到它。
下一个程序给你展示缓冲的效果:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main()
{
int i;
char *sep = "";
for (i = 0; i < 10; i++) {
printf("%s%d", sep, i);
sep = ", ";
sleep(1);
}
printf("\n");
}
假设您将看到(在终端上)该程序,将数字0 写入9,以, 分隔,并以一秒为间隔。
但是由于缓冲,您观察到的情况完全不同,您将看到您的程序如何等待 10 秒而不在终端上写入任何内容,最后一次写入所有内容,包括最后行结束,当程序终止时,shell 会再次向您显示提示。
如果你把程序改成这样:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main()
{
int i;
char *sep = "";
for (i = 0; i < 10; i++) {
printf("%s%d", sep, i);
fflush(stdout);
sep = ", ";
sleep(1);
}
printf("\n");
}
您将看到预期的输出,因为您已告诉stdio 在每次循环通过时刷新缓冲区。在这两个程序中,您对 printf(3) 进行了 10 次调用,但最后只有一个 write(2) 来写入完整的缓冲区。在第二个版本中,您强制 stdio 在每个 printf 之后执行一个这样的 write(2),并在程序通过循环时显示数据。
小心,因为stdio 的另一个特性可能会让您感到困惑,例如printf(3),当您打印到终端设备时,会刷新每个\n 的输出,但是当您通过管道运行它时,它只有当缓冲区完全填满时才会这样做。这节省了系统调用(例如,在 FreeBSD 中,stdio 选择的缓冲区大小约为 32kb,足以将两个块强制为write(2) 和最佳值(超过该大小不会变得更好)