为什么打印到stdout这么慢?可以加速吗?

Rus*_*uss 159 python linux printing stdout tty

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

能以某种方式加速写入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
Run Code Online (Sandbox Code Playgroud)

哇.为了确保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
Run Code Online (Sandbox Code Playgroud)

所以它不是蟒蛇技巧,它只是终端.我总是知道将输出转储到/ 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
Run Code Online (Sandbox Code Playgroud)

这当然更好(~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
Run Code Online (Sandbox Code Playgroud)

Pi *_*ort 148

如何写入物理磁盘比写入"屏幕"(可能是一个全RAM操作)更快,并且实际上就像使用/ dev/null简单地转储到垃圾一样快?

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

磁盘看起来更快,因为它是高度缓冲的:所有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
Run Code Online (Sandbox Code Playgroud)

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

但是,通过上面对无缓冲写入的修改,只需要40秒即可将1000行写入磁盘.我放弃了等待10万行写入,但从前一次推断,将需要一个多小时.

这让终端的11秒进入视角,不是吗?

所以要回答你原来的问题,写一个终端实际上​​速度非常快,所有事情都要考虑,而且没有太大的空间让它更快(但是个别终端的工作量确实不同;请参阅Russ对此的评论回答).

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

  • 请注意,这个答案是误导和错误的(对不起!).具体而言,说"没有太多空间让它更快[超过11秒]"是错误的.请看我自己的问题答案,我在那里表明wterm终端在0.26s时达到了相同的11s结果. (8认同)
  • 我得到I/O缓冲...你当然提醒我,我应该有fsync'd真正比较完成时间(我会更新问题),但fsync _per line_是精神错乱.tty真的需要有效地做到这一点吗?是否没有与文件等效的终端/操作系统端缓冲?即:应用程序写入stdout并在终端呈现屏幕之前返回,终端(或操作系统)将其全部缓冲.然后终端可以合理地使尾部以可见的帧速率进行屏幕显示.在每条线路上有效阻挡似乎很愚蠢.我觉得我还在遗漏一些东西. (6认同)
  • Russ:感谢您的反馈!在我这边,一个更大的`fdopen`缓冲区(2MB)肯定产生了巨大的差异:它将打印时间从几秒钟缩短到0.05秒,与文件输出相同(使用`gnome-terminal`). (2认同)

Rus*_*uss 85

感谢所有的评论!我最后在你的帮助下自己回答了这个问题.但是,回答你自己的问题感觉很脏.

问题1:为什么打印到stdout会变慢?

答:打印到stdout本身并不慢.你工作的终端很慢.它与应用程序端的I/O缓冲几乎没有关系(例如:python文件缓冲).见下文.

问题2:可以加速吗?

答:可以,但似乎不是来自程序方面(对'stdout'进行'打印'的一方).要加快速度,请使用速度更快的终端仿真程序.

说明...

我尝试了一个自称为"轻量级"的终端程序,wterm并获得了明显更好的结果.下面是我的测试脚本的输出(在问题的底部),wterm在1920x1200的同一系统上运行,其中基本打印选项使用gnome-terminal需要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是慢的.

所以 - 如果你有,你觉得是缓慢的一个长期运行的脚本,它喷出的文本大量到stdout ...尝试不同的终端,看看它是否是更好!

请注意,我几乎随机wterm从ubuntu/debian存储库中提取. 这个链接可能是同一个终端,但我不确定.我没有测试任何其他终端模拟器.


更新:因为我不得不划伤痒,我测试了一堆其他终端模拟器,使用相同的脚本和全屏(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

记录的时间是手动收集的,但它们非常一致.我记录了最佳(ish)值.YMMV,显然.

作为奖励,这是一个有趣的游览一些可用的各种终端模拟器!我很惊讶我的第一个"替代"测试结果是最好的.

  • 此外,`screen`,(程序)应该包含在列表中!(或者`byobu`,它是带有增强功能的`screen`的包装器)这个实用程序允许有几个终端,很像X终端中的标签.我认为打印到当前`screen`的终端与打印到普通的终端相同,但是在`screen`的终端中打印然后切换到没有活动的另一个终端呢? (3认同)
  • OSX上的iTerm2给了我:`print:0.587 s,write to file(+ fsync):0.034 s,print with stdout =/dev/null:0.041 s`.并且在iTerm2中运行'screen':`print:1.286 s,写入文件(+ fsync):0.043 s,使用stdout打印=/dev/null:0.033 s` (2认同)

Has*_*kun 13

您的重定向可能无效,因为程序可以确定它们的输出FD是否指向tty.

当指向终端时,stdout可能是行缓冲的(与C的stdout流行为相同).

作为一个有趣的实验,尝试输出到输出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
Run Code Online (Sandbox Code Playgroud)