返回

打印到 stdout 为什么这么慢?原因和缓解措施详解

Linux

## 打印到 stdout 为什么这么慢?

对于使用 print 语句将内容输出到终端时所花费的时间之长,你感到惊讶和沮丧吗?你并不孤单。最近一次痛苦的缓慢日志记录让我对这个问题进行了研究,惊讶地发现,几乎所有花费的时间都在等待终端处理结果。

### 问题所在

为了了解问题所在,我编写了一个 Python 脚本(附在文章末尾),比较了向 stdout、文件写入和将 stdout 重定向到 /dev/null 的时间。结果令人震惊:

  • 打印到 stdout:11.950 秒
  • 写入文件(+ fsync):0.122 秒
  • 打印到 /dev/null:0.050 秒

显然,终端是罪魁祸首。我进行了进一步的实验,确保 Python 不会在后台做一些事情,比如识别我将 stdout 重新分配给了 /dev/null。结果证实,问题出在终端本身。

### 为什么终端这么慢?

我总是知道将输出转储到 /dev/null 可以加快速度,但从未想到它如此重要!我无法理解为什么将内容写入物理磁盘比写入“屏幕”(基本上是基于 RAM 的操作)要快得多,而且实际上与使用 /dev/null 将内容转储到垃圾中一样快。

据我所知,终端如何阻塞 I/O 的解释如下:以便它可以“解析 [输入]、更新其帧缓冲区、与 X 服务器通信以滚动窗口等……”。但我仍然不完全明白。什么可能需要这么长时间?

### 有没有解决办法?

我希望有一个解决办法,但我并不乐观。除非有一个更快的 tty 实现?在继续阅读了一些评论后,我意识到屏幕尺寸也会影响打印时间。将我的 Gnome 终端放大到 1920x1200 时,打印时间会增加约 4 倍。

这让我更加疑惑,因为我不明白为什么终端屏幕渲染会减慢应用程序写入 stdout 的速度。为什么我的程序需要等待屏幕渲染才能继续?

难道所有终端/tty 应用程序都不是生而平等的吗?我还没有做实验。在我看来,终端应该能够缓冲所有传入的数据,在不知不觉中对其进行解析/渲染,并且只渲染在当前屏幕配置中可见的最新的块,且以合理的帧速率进行渲染。

因此,如果我可以在约 0.1 秒内将内容写入磁盘并对其进行 fsync,那么终端应该能够在类似的时间内完成相同的操作(可能在执行此操作时进行了少量屏幕更新)。

我仍然希望有一种 tty 设置可以从应用程序方面进行更改,以改善程序员对此行为的体验。如果这严格来说是一个终端应用程序问题,那么它甚至不属于 StackOverflow?

我仍然没有答案。欢迎提出你的见解。

## 解决方法

虽然没有直接的解决方案,但有以下一些缓解措施可以帮助你减少打印到 stdout 的时间:

  • 使用日志记录库: 日志记录库(如 Python 的 logging)可以帮助你缓冲输出并以更有效的方式写入它。
  • 使用异步 I/O: 异步 I/O 允许你的应用程序在等待 I/O 操作完成时执行其他任务,从而提高性能。
  • 使用多进程: 如果你需要打印大量的数据,可以创建多个进程来并行执行任务。
  • 使用 /dev/null 如果你不需要在屏幕上查看输出,可以将 stdout 重定向到 /dev/null 以提高性能。

## 常见问题解答

1. 为什么将内容写入磁盘比写入 stdout 快?

  • 终端必须解析和渲染输入,这需要时间。磁盘 I/O 操作则相对直接。

2. 我可以更改 tty 设置来提高性能吗?

  • 目前还没有已知的 tty 设置可以从应用程序方面改善性能。

3. 是否有更快的 tty 实现?

  • 我还没有听说过任何更快的 tty 实现,但这是一个有趣的研究课题。

4. 打印到 stdout 的最佳做法是什么?

  • 如果可能,使用日志记录库。
  • 考虑使用异步 I/O 或多进程。
  • 如果不需要在屏幕上查看输出,可以将 stdout 重定向到 /dev/null

5. 什么时候应该避免打印到 stdout?

  • 当需要在屏幕上查看输出时。
  • 当性能至关重要时,例如在实时系统中。

## 总结

打印到 stdout 可能会很慢,原因是终端 I/O 阻塞。虽然没有直接的解决方案,但有缓解措施可以帮助你减少打印时间。我希望本文能够帮助你更好地理解这个问题,并找到适合你的应用程序的解决方案。