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

5

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

我该如何使用write()fwrite()将数据输出到控制台而不是文件?

总体而言,我的设置包括使用C编写的程序,其输出进入Python脚本,其中数据进一步处理。我形成了一个管道:

./program_in_c | script_in_python

这样做能够在树莓派上利用更多的处理器内核,从而带来额外的好处。

3个回答

4
#include <unistd.h>

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

write()函数从缓冲区buf开始写入最多count个字节到由文件描述符fd指定的文件中。

在Linux中,标准输出文件描述符至少为1!在调用write系统调用之前,务必使用flush函数清除stdoutput缓冲区,以确保清除所有先前的垃圾。

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

函数fwrite()将由指针ptr指向的位置获取的nmemb个大小为size字节的数据项写入到由指针stream指向的流中。
fflush(stdout);
int buf[8];
fwrite(buf, sizeof(int), sizeof(buf), stdout);

请参考下面的链接阅读man页面以获取更多信息:

fwrite

write


@ryyker- 当然没问题!我正在修正答案。我已将其更改为int,因为我想在这个例子中展示sizeof的用法。谢谢。 - Adam
2
调用 write 然后是 fflush(stdout) 的顺序是错误的。write 总是立即写入而不缓冲。而 stdout 并不知道这一点。因此,如果您使用了 stdout,您可能需要在调用 write 之前写入缓冲区中的任何内容(通过调用 fflush)。 - user253751
2
write()fflush()没有任何关系,恐怕你的回答完全不正确。 - Luis Colorado
@LuisColorado - 感谢您,我已经将fflush移动到前面,以确保在写入之前清除缓冲区中的所有先前垃圾数据。 - Adam

3

尝试克服已使用的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的“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");
}

假设你将在终端上看到(程序中)写出由标记09的数字,以,分隔,并以一秒为间隔。

但由于缓冲区,所观察到的与此相当不同。你会看到你的程序等待了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 在每次循环经过时刷新缓冲区。在两个程序中,你都调用了10次printf(3),但仅有一个write(2)在最后写入整个缓冲区。在第二个版本中,你强制 stdio 每次printf之后做一个这样的write(2),从而显示程序通过循环时的数据输出情况。
要小心了,因为另一个与stdio相关的特性可能会让你感到困惑,当你向终端设备打印时,printf(3)会在每个\n处刷新输出,但当你将其通过管道运行时,它只在缓冲区完全填满时才这样做。这样可以节省系统调用(例如,在FreeBSD中,stdio选择的缓冲区大小约为32kb,足够强制两个块write(2)并且达到最优效果(超过该大小你将不会获得更好的效果)。

1
我的天啊,我知道的太少了!感谢您深入的回答。看来我的方法完全不好,试图通过这种方式传递数据。您能否建议什么比这更好?请注意,这是一个实时数据流。我应该尝试将其保存到文件中(在C程序中),并同时从文件中读取(使用Python)吗?如果它增长得太多怎么办(我需要传输3个字节x 2100 SPS x 3个通道)?是否可以使用内存创建管道作为媒介?我这里不需要完整的答案,只需要一个提示,我会自己探索和寻找更多的知识/解决方案。 - smajli
1
管道通常用于这种数据流。最好使用管道,但不必担心用于打印数据的例程。只需使用stdio输出格式化的流,并从下一个进程中读取它。使用管道时,数据通常不会存储到磁盘中。 - Luis Colorado

2

C语言中的控制台输出几乎与文件相同。一旦您包含了stdio.h,就可以在控制台输出上写入,名称为stdout(代表“标准输出”)。最终,以下语句:

printf("hello world!\n");

等同于:

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

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

1
根据定义,sizeof(char) 总是为 1。(在使用“总是”这个词比较安全的少数几个地方之一 :) ) - ryyker
2
@ryyker 当然是1。但我的首要目标是使我的答案易于理解和实质性的。x) - eliottness
不太对,你的第二段代码片段也会写入一个0字节。fflush是否需要? - user253751

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