为什么打印到stdout这么慢?能加快速度吗?

2024-09-27 07:31:20 发布

您现在位置:Python中文网/ 问答频道 /正文

我总是对用打印语句简单地输出到终端需要多长时间感到惊讶/沮丧。在最近几次痛苦而缓慢的日志记录之后,我决定对其进行研究,并惊讶地发现几乎所有的时间都在等待终端处理结果。

给stdout写信能加快速度吗?

我写了一个脚本('print_timer.py'在这个问题的底部)来比较将100k行写入stdout、文件和将stdout重定向到/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有多慢。怎么可能写物理磁盘比写“screen”(可能是一个全RAM操作)快得多,而且有效地像简单地用/dev/null转储垃圾一样快?

This link讨论终端将如何阻塞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),但不会改变我的问题。它只会在我的问题中添加,因为我不明白为什么终端屏幕呈现会减慢应用程序向stdout的写入速度。为什么我的程序需要等待屏幕渲染继续?

所有终端/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

Tags: devcmd终端timestdoutsyslinesummary
3条回答

How can it be that writing to physical disk is WAY faster than writing to the "screen" (presumably an all-RAM op), and is effectively as fast as simply dumping to the garbage with /dev/null?

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

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

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

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

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

我在我的机器上运行了你的文件写测试,在缓冲的情况下,这里也有10万行的0.05秒。

但是,通过上述修改,可以无缓冲地写入,只需40秒就可以将1000行写入磁盘。我放弃了等待10万行的写作,但根据前面的推断,要花上一个小时。

这让终端有11秒的时间,不是吗?

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

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

谢谢你的评论!我最终在你的帮助下自己回答了。不过,回答你自己的问题让人觉得很肮脏。

问题1:为什么打印到标准输出速度慢?

回答:打印到stdout是而不是固有的速度慢。你工作的终端是慢的。它与应用程序端的I/O缓冲(例如:python文件缓冲)几乎没有关系。见下文。

问题2:能否加快速度?

回答:可以,但似乎不是从程序方面(从“打印”到标准输出的方面)。要加快速度,请使用速度更快的不同终端仿真器。

解释。。。

我尝试了一个自称“轻量级”的终端程序,名为wterm,得到了显著的效果。下面是我的测试脚本(在问题的底部)在1920x1200英寸的同一个系统上运行时的输出,在该系统中,使用gnome终端的基本打印选项花费了12秒:

-----
timing summary (100k lines each)
-----
print                         : 0.261 s
write to file (+fsync)        : 0.110 s
print with stdout = /dev/null : 0.050 s

0.26秒比12秒好多了!我不知道wterm是否更聪明,它是如何呈现屏幕,按照我的建议(以合理的帧速率呈现“可见”尾部),还是它只是“做得比gnome-terminal少”。不过,就我的问题而言,我得到了答案。gnome-terminal是慢的。

所以-如果你有一个长时间运行的脚本,你觉得它是缓慢的,它喷出大量的文本到标准输出。。。换个终端试试看有没有更好的!

注意,我从ubuntu/debian存储库中随机抽取了wtermThis link可能是同一个终端,但我不确定。我没有测试任何其他终端仿真器。


更新:因为我不得不抓痒,我用相同的脚本和全屏(1920x1200)测试了一整堆其他终端模拟器。我手动收集的数据如下:

wterm           0.3s
aterm           0.3s
rxvt            0.3s
mrxvt           0.4s
konsole         0.6s
yakuake         0.7s
lxterminal        7s
xterm             9s
gnome-terminal   12s
xfce4-terminal   12s
vala-terminal    18s
xvt              48s

记录的时间是手动收集的,但它们非常一致。我记录了最好的值。很明显是YMMV。

作为奖励,这是一个有趣的旅行,一些不同的终端仿真器提供了那里!我很惊讶我的第一次“替补”考试竟然是最好的。

由于程序可以确定其输出FD是否指向tty,因此重定向可能什么也不做。

很可能stdout在指向终端时是行缓冲的(与C的^{}流行为相同)。

作为一个有趣的实验,尝试将输出设置为cat


我试过自己有趣的实验,结果如下。

$ python test.py 2>foo
...
$ cat foo
-----
timing summary (100k lines each)
-----
print                         : 6.040 s
write to file                 : 0.122 s
print with stdout = /dev/null : 0.121 s

$ python test.py 2>foo |cat
...
$ cat foo
-----
timing summary (100k lines each)
-----
print                         : 1.024 s
write to file                 : 0.131 s
print with stdout = /dev/null : 0.122 s

相关问题 更多 >

    热门问题