如何使用 write() 或 fwrite() 将数据写入终端(stdout)?

How to use write() or fwrite() for writing data to terminal (stdout)?

我正在尝试加速我的 C 程序以更快地吐出数据。
目前我正在使用 printf() 向外界提供一些数据。它是连续的数据流,因此我无法使用 return(data).

如何使用 write()fwrite() 将数据提供给 console 而不是文件?

总的来说,我的设置由用 C 编写的程序组成,其输出转到 python 脚本,数据在该脚本中得到进一步处理。我形成一个管道:

./program_in_c | script_in_python

这通过使用更多处理器内核为 Raspberry Pi 带来额外好处。

#include <unistd.h>

       ssize_t write(int fd, const void *buf, size_t count);

write() writes up to count bytes from the buffer starting at buf to the file referred to by the file descriptor fd.

标准输出文件描述符是:至少 linux 中有 1 个! 在调用 write 系统调用之前也使用刷新标准输出缓冲区以确保清除所有以前的垃圾

fflush(stdout); // Will now print everything in the stdout buffer
write(1, buf, count);

使用 fwrite:

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

The function fwrite() writes nmemb items of data, each size bytes long, to the stream pointed to by stream, obtaining them from the location given by ptr.

fflush(stdout);
int buf[8];
fwrite(buf, sizeof(int), sizeof(buf), stdout);

请参阅以下链接中的手册页以进一步阅读:

fwrite

write

C 中控制台输出的工作方式几乎与文件相同。包含 stdio.h 后,您可以在控制台输出上写入,命名为 stdout(“标准输出”)。最后声明如下:

printf("hello world!\n");

等同于:

char str[] = "hello world\n";

fwrite(str, sizeof(char), sizeof(str) - 1, stdout);
fflush(stdout);

好吧,试图克服 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) 混合使用 --- 正如我'我已经看到建议您在 write(2) 之后使用 fflush(3),这是完全不连贯的---不缓冲数据)没有收益(如果您这样做,您可能会得到错误排序的输出部分使用 printf(3) 的调用和部分使用 write(2) 的调用(这种情况在您计划执行的管道中发生得更多,因为缓冲区不是 面向行的 --- stdio 中缓冲输出的另一个特点---)

最后,我推荐您阅读 Dennis Ritchie 和 Rob Pike 合着的“The Unix programming environment”。它会教你很多 unix,但一件非常好的事情是它会教你完美地使用 stdio 包和 unix 文件系统调用读写。运气好的话,您会在 Internet 上以 .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) 和最佳(超过该大小你不会变得更好)