打印到 stdout 为什么这么慢?原因和缓解措施详解
2024-03-13 22:44:32
## 打印到 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 阻塞。虽然没有直接的解决方案,但有缓解措施可以帮助你减少打印时间。我希望本文能够帮助你更好地理解这个问题,并找到适合你的应用程序的解决方案。