C: 为什么 fprintf(stdout,....) 很慢?

12

我仍然经常使用控制台输出来了解代码中正在发生的情况。

我知道这可能有点老式,但我也使用它来将stdout“导入”日志文件等。

然而,事实证明,控制台输出的速度会因某种原因而变慢。我想知道有人能否解释一下为什么fprintf()到控制台窗口似乎是阻塞的。

我迄今为止所做/诊断的内容:

  1. 我测量了一个简单的操作所需的时间 fprintf(stdout,"quick fprintf\n"); 需要:0.82毫秒(平均值)。由于vsprintf_s(...)仅在几微秒内将相同的输出写入字符串,因此这被认为是太长了。因此必须有特定于控制台的一些阻塞。

  2. 为了逃避这种阻塞,我使用vsprintf_s(...)将我的输出复制到类似fifo的数据结构中。该数据结构由关键部分对象保护。然后,一个单独的线程通过将排队的输出放入控制台来取消排队数据结构。

  3. 通过引入管道服务,我可以获得进一步的改进。我的程序的输出(假定最终显示在控制台窗口中)经过以下方式:

    • 使用vsprintf_s(...)格式化输出为简单字符串。
    • 将字符串排队到类似fifo的数据结构中,例如链表结构。该数据结构由关键部分对象保护。
    • 第二个线程通过将输出字符串发送到命名管道来取消对数据结构的排队。
    • 第二个进程读取命名管道并将字符串再次放入类似fifo的数据结构中。这是为了使阅读远离阻塞的控制台输出。读取过程快速读取命名管道并连续监视管道缓冲区的填充级别。
    • 第二个进程中的第二个线程最终通过fprintf(stdout,...)向控制台取消排除数据结构。

我有两个至少有两个线程的进程,它们之间有一个命名管道,管道两端都有类似fifo的数据结构以避免管道缓冲区满时阻塞。

这些步骤都是为了确保控制台输出是“非阻塞”的,但结果还不错。我的主程序可以在几微秒内写入复杂的fprintf(stdout,...)。

也许我应该早点问:是否有其他(更简单!)的方法来实现非阻塞控制台输出?


6
我怀疑是控制台太慢了。终端模拟器往往速度较慢(因为其波特率是其规格的一部分)。你是否只是在将stdout重定向到某个文件时测量时间?您可以将调试信息输出到其他FILE(并使用具有大缓冲区的setvbuf)。 - Basile Starynkevitch
@ larsmans:在这种情况下,时间对我来说很有趣,因为它会延迟我的代码执行。正如所说:我可以使用vsprintf(...)达到几微秒,并且我观察到使用fprintf(stdout,...)几乎需要一毫秒的时间。因此,大部分时间将花费在等待输出通道上。 - Arno
不确定是否有帮助,但在Linux上使用行缓冲的stdout,平均每次运行10000次调用fprintf(stdout,“quick fprintf\n”);需要0.000023904秒。 - Maxim Egorushkin
@MaximYegorushkin:大多数Linux终端仿真器都已经针对快速输出进行了优化,例如:如果我没记错的话,Konsole在显示屏上显示任何内容之前会等待多达50毫秒以获取更多的输入。 - ninjalj
@ninjalj 只是为了记录,我正在使用Emacs Shell模式,而不是终端仿真器。 - Maxim Egorushkin
显示剩余6条评论
1个回答

13

我认为时间问题与控制台默认是 行缓冲 有关。这意味着每次向其中写入 '\n' 字符时,整个输出缓冲区将被发送到控制台,这是一种相当耗费资源的操作。这是你立即在输出中看到该行的代价。

你可以通过更改缓冲策略为 全缓冲 来改变此默认行为。其结果是,输出将以与您的缓冲区大小相等的块发送到控制台,但单个操作将更快完成。

在首次将内容写入控制台之前进行以下调用:

char buf[10000];
setvbuf(stdout, buf, _IOFBF, sizeof(buf));

单个写操作的时间应该会提高,但输出不会立即出现在控制台上。这对于调试来说并不是太有用,但时间会得到改善。如果您设置一个线程,定期调用fflush(stdout),比如每秒钟一次,您应该能够在单个写操作的性能和程序输出与实际在控制台上看到它之间的延迟之间获得合理的平衡。


测试过了,确认无误,非常好的答案。我希望有一些技巧可以加快这个过程。 - Arno
当然,这应该使用 sizeof buf 而不是重复魔术常量。 :) - unwind
@unwind 当然,你是对的!这是我的明显疏忽,当你看到我做出这样愚蠢的事情时,欢迎编辑我的答案 :) 非常感谢! - Sergey Kalinichenko
@dasblinkenlight:在缓冲区满或执行fflush(stdout)之前,不会有任何输出。因此,必须有额外的线程执行刷新。 - Arno

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