小编典典

为什么打印到标准输出这么慢?可以提速吗?

all

我一直对使用打印语句简单地输出到终端需要多长时间感到惊讶/沮丧。在最近一些令人痛苦的缓慢日志记录之后,我决定研究它,并且非常惊讶地发现几乎 所有
时间都花在等待终端处理结果上。

可以以某种方式加快写入标准输出的速度吗?

我写了一个脚本(print_timer.py这个问题底部的’‘)来比较将 100k 行写入标准输出、文件和标准输出重定向到/dev/null.
以下是计时结果:

$ python print_timer.py
this is a test
this is a test
<snipped 99997 lines>
this is a test
-----
timing summary (100k lines each)
-----
print                         :11.950 s
write to file (+ fsync)       : 0.122 s
print with stdout = /dev/null : 0.050 s

哇。为了确保python没有在幕后做一些事情,比如认识到我将stdout重新分配给/dev/null之类的,我在脚本之外做了重定向......

$ python print_timer.py > /dev/null
-----
timing summary (100k lines each)
-----
print                         : 0.053 s
write to file (+fsync)        : 0.108 s
print with stdout = /dev/null : 0.045 s

所以这不是python技巧,它只是终端。我一直都知道将输出转储到 /dev/null 会加快速度,但从未想过它有那么重要!

tty 的速度让我感到惊讶。为什么写入物理磁盘比写入“屏幕”(可能是全 RAM 操作)快得多,并且实际上与使用 /dev/null 转储到垃圾中一样快?

此链接讨论终端将如何阻止
I/O,以便它可以 “解析 [输入]、更新其帧缓冲区、与 X 服务器通信以滚动窗口等” ......但我不完全明白。什么可以花这么长时间?

我希望没有出路(缺少更快的 tty 实现?)但我还是会问。


更新:在阅读了一些评论后,我想知道我的屏幕尺寸实际上对打印时间有多大影响,而且它确实有一些意义。上面真正慢的数字是我的 Gnome 终端被炸到
1920x1200。如果我把它缩小得很小,我会得到......

-----
timing summary (100k lines each)
-----
print                         : 2.920 s
write to file (+fsync)        : 0.121 s
print with stdout = /dev/null : 0.048 s

这当然更好(~4x),但不会改变我的问题。它只会 增加
我的问题,因为我不明白为什么终端屏幕渲染会减慢应用程序写入标准输出的速度。为什么我的程序需要等待屏幕渲染才能继续?

所有的终端/tty
应用程序都不是平等的吗?我还没有做实验。在我看来,终端应该能够缓冲所有传入的数据,不可见地解析/渲染它,并且仅以合理的帧速率渲染当前屏幕配置中可见的最新块。因此,如果我可以在
~0.1 秒内将 +fsync 写入磁盘,则终端应该能够以该顺序完成相同的操作(在执行此操作时可能会进行一些屏幕更新)。

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

我错过了什么?


这是用于生成计时的python程序:

import time, sys, tty
import os

lineCount = 100000
line = "this is a test"
summary = ""

cmd = "print"
startTime_s = time.time()
for x in range(lineCount):
    print line
t = time.time() - startTime_s
summary += "%-30s:%6.3f s\n" % (cmd, t)

#Add a newline to match line outputs above...
line += "\n"

cmd = "write to file (+fsync)"
fp = file("out.txt", "w")
startTime_s = time.time()
for x in range(lineCount):
    fp.write(line)
os.fsync(fp.fileno())
t = time.time() - startTime_s
summary += "%-30s:%6.3f s\n" % (cmd, t)

cmd = "print with stdout = /dev/null"
sys.stdout = file(os.devnull, "w")
startTime_s = time.time()
for x in range(lineCount):
    fp.write(line)
t = time.time() - startTime_s
summary += "%-30s:%6.3f s\n" % (cmd, t)

print >> sys.stderr, "-----"
print >> sys.stderr, "timing summary (100k lines each)"
print >> sys.stderr, "-----"
print >> sys.stderr, summary

阅读 80

收藏
2022-07-09

共1个答案

小编典典

为什么写入物理磁盘比写入“屏幕”(可能是全 RAM 操作)快得多,并且实际上与使用 /dev/null 转储到垃圾中一样快?

恭喜,您刚刚发现了 I/O 缓冲的重要性。:-)

磁盘似乎更快,因为它是高度缓冲的:所有 Pythonwrite()调用都在实际写入物理磁盘之前返回。(操作系统稍后会这样做,将数千个单独的写入组合成一个大而有效的块。)

另一方面,终端很少或没有缓冲:每个人print/write(line)等待完整的写入(即显示到输出设备)完成。

为了使比较公平,您必须使文件 test 使用与终端相同的输出缓冲,您可以通过将示例修改为:

fp = file("out.txt", "w", 1)   # line-buffered, like stdout
[...]
for x in range(lineCount):
    fp.write(line)
    os.fsync(fp.fileno())      # wait for the write to actually complete

我在我的机器上运行了你的文件写入测试,并且通过缓冲,它在这里也是 0.05 秒 100,000 行。

但是,通过上述对无缓冲写入的修改,仅将 1,000 行写入磁盘需要 40 秒。我放弃了等待 100,000 行来写,但从前面推断,它需要一个多小时

这让终端机的 11 秒变得清晰起来,不是吗?

因此,要回答您最初的问题,考虑到所有因素,写入终端实际上非常快,并且没有太多空间可以让它更快(但各个终端的工作量确实有所不同;请参阅 Russ 对此的评论回答)。

(您可以添加更多的写入缓冲,例如使用磁盘 I/O,但在刷新缓冲区之前,您不会看到写入终端的内容。这是一种权衡:交互性与批量效率。)

2022-07-09