【问题标题】:How to use write() or fwrite() for writing data to terminal (stdout)?如何使用 write() 或 fwrite() 将数据写入终端(stdout)?
【发布时间】:2020-10-23 05:03:20
【问题描述】:

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

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

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

./program_in_c | script_in_python

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

【问题讨论】:

    标签: c pipeline


    【解决方案1】:
    #include <unistd.h>
    
           ssize_t write(int fd, const void *buf, size_t count);
    

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

    标准输出文件描述符是:至少在 linux 中是 1! 在调用 write 系统调用以确保清除所有先前的 garabge 之前,还要注意使用刷新 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() 写入 nmemb 项数据,每个 size 字节 long,指向stream指向的流,从 ptr 给出的位置。

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

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

    fwrite

    write

    【讨论】:

    • @ryyker- 确定!修复答案。我已将其更改为 int cos 我想在此示例中显示sizeof 的使用。谢谢
    • 调用write 然后fflush(stdout) 是错误的方法。 write 总是立即写入而不缓冲。而标准输出对此一无所知。因此,您可能希望在调用 write 之前写入缓冲区中的任何内容(通过调用 fflush),如果您也一直在使用 stdout。
    • write() 恐怕与`fflush() 无关。你的答案根本不正确。
    • @LuisColorado - 谢谢你我之前移动了 fflush 以确保缓冲区中所有以前的垃圾数据在写入之前都被清除
    【解决方案2】:

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

    printf("hello world!\n");
    

    等同于:

    char str[] = "hello world\n";
    
    fwrite(str, sizeof(char), sizeof(str) - 1, stdout);
    fflush(stdout);
    

    【讨论】:

    • sizeof(char) 根据定义始终为1。 (使用always这个词的少数几个地方之一是相当安全的:))
    • @ryyker 当然是 1。但我的主要目标是让我的答案易于理解和实质性。 x)
    • 不完全是,你的第二个代码 sn-p 也写了一个 0 字节。需要 fflush 吗?
    【解决方案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) ——正如我所看到的那样推荐你使用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) 和最佳值(超过该大小不会变得更好)

    【讨论】:

    • 天哪,我知道的太少了!感谢您的深入回答。通过尝试以这种方式传递数据,我的方法似乎一点也不好。你能建议什么会更好吗?请注意,这是一个实时数据流。我应该尝试保存到文件(在 C 程序中)并同时从中读取(python)吗?如果它增长太多(我需要传输 3 个字节 x 2100 SPS x 3 个通道)怎么办?是否可以使用内存作为介质创建管道?我这里不需要完整的答案,只是一个提示,我会自己探索并尝试找到更多的知识/解决方案。
    • 管道通常是这种数据流所需要的。最好是使用管道,但不要担心用于打印数据的例程。只需使用stdio 输出格式化流并从下一个进程中读取。数据通常不会使用管道进入磁盘。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多