为什么打印输出速度太慢? 它可以加快吗?

我总是惊讶/沮丧,用打印语句简单地输出到terminal需要多长时间。 经过最近的一些痛苦的缓慢采伐之后,我决定调查一下,发现几乎所有的时间都在等待terminal处理结果,我感到非常惊讶。

可以写入标准输出速度加快?

我写了一个脚本(在这个问题的底部的' print_timer.py ')来比较将100k行写入标准输出到文件,并将标准输出redirect到/dev/null时的时序。 这是时间的结果:

  $ python print_timer.py
这是一个testing
这是一个testing
 <剪切99997行>
这是一个testing
 -----
时间总结(每个10万行)
 -----
打印:11.950秒
写入文件(+ fsync):0.122秒
用stdout = / dev / null打印:0.050秒 

哇。 为了确保python不会在幕后做一些事情,比如认识到我把stdout重新分配给了/ dev / null或者什么的,我在脚本之外做了redirect。

  $ python print_timer.py> / dev / null
 -----
时间总结(每个10万行)
 -----
打印:0.053秒
写入文件(+ fsync):0.108秒
用stdout = / dev / null打印:0.045秒

所以这不是一个python技巧,它只是terminal。 我一直知道倾销输出到/ dev / null加快了速度,但从来没有想到这是显着的!

令我惊讶的是,tty有多慢。 如何写入物理磁盘比写入“屏幕”(大概是一个全RAM操作)更快,并且实际上就像使用/ dev / null简单地转储到垃圾一样快?

这个链接讲的是如何terminal将阻止I / O,所以它可以“parsing[input],更新其帧缓冲区,与X服务器通信,以滚动窗口等” …但我不充分得到它。 有什么可以采取这么久?

我希望没有出路(缺less一个更快的tty实现?),但是我仍然会问。


更新:在阅读了一些评论之后,我想知道我的屏幕尺寸对打印时间有多大的影响,这确实有一些意义。 上面的真正慢的数字是我的Gnometerminal吹到1920×1200。 如果我减less很小,我得到…

  -----
时间总结(每个10万行)
 -----
打印:2.920秒
写入文件(+ fsync):0.121秒
用stdout = / dev / null打印:0.048秒

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

是不是所有的terminal/ tty应用程序创build的平等? 我还没有试验。 在我看来,terminal似乎应该能够缓冲所有传入的数据,不可见地进行parsing/渲染,并且只能以合理的帧速率渲染当前屏幕configuration中可见的最近的块。 所以如果我可以在〜0.1秒内将+ fsync写入磁盘,terminal应该能够按照这个顺序完成相同的操作(也许在屏幕更新的同时进行几次)。

我仍然希望有一个tty设置可以从应用程序端改变,使这种行为更好的程序员。 如果这是严格的terminal应用程序问题,那么这可能甚至不属于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.3fs\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.3fs\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.3fs\n" % (cmd, t) print >> sys.stderr, "-----" print >> sys.stderr, "timing summary (100k lines each)" print >> sys.stderr, "-----" print >> sys.stderr, summary 

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

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

磁盘似乎更快,因为它是高度缓冲的:所有Python的write()调用在任何事物被写入物理磁盘之前都会返回。 (操作系统稍后会将数千个单独的写入操作合并为一个高效的块。)

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

为了使比较公平,您必须使文件testing使用与terminal相同的输出缓冲,您可以通过修改您的示例来执行以下操作:

 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 

我在我的机器上运行你的文件写入testing,并用缓冲,这也是0.05s在这里为100,000线。

但是,通过上面的修改来编写无缓冲区,只需要40秒就可以将1000行写入磁盘。 我放弃了等待10万行的写法,但从以前的推断,这将需要一个多小时

这使terminal的11秒的透视,不是吗?

所以要回答你原来的问题,写一个terminal实际上​​是非常快的,所有的事情都考虑到了,而且没有太多的空间让它变得更快(但是个人terminal的确做了很多改变,参见Russ对此的评论回答)。

(你可以添加更多的写缓冲,就像使用磁盘I / O一样,但是直到缓冲区被刷新之后才能看到写到terminal的内容,这是一个折衷:交互性与整体效率之间的关系。

感谢所有的评论! 我已经结束了自己回答你的帮助。 尽pipe如此,感觉肮脏的回答你自己的问题。

问题1:为什么打印速度慢?

答:打印到标准输出不是固有的缓慢。 与你一起工作的terminal很慢。 而且在应用程序端的I / O缓冲(例如:python文件缓冲)几乎没有任何影响。 见下文。

问题2:可以加快吗?

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

说明…

我尝试了一个名为wterm的自我描述的“轻量级”terminal程序,并获得了明显更好的结果。 下面是我的testing脚本的输出(在问题的底部),当运行在wterm在1920×1200在基本的打印选项花了12s使用GNOMEterminal相同的系统:

 -----
时间总结(每个10万行)
 -----
打印:0.261秒
写入文件(+ fsync):0.110秒
用stdout = / dev / null打印:0.050秒

0.26s比12s好多了! 我不知道wterm是否更加智能化了它是如何根据我所build议的方式(以合理的帧速率渲染“可见的”尾部),或者它是否比gnome-terminal 。 不过,为了我的问题,我已经得到了答案。 gnome-terminal很慢。

所以 – 如果你有一个长时间运行的脚本,你觉得很慢,它会输出大量的文本标准输出…尝试一个不同的terminal,看看它是否更好!

请注意,我几乎从Ubuntu / Debian版本库中随机抽取wterm 。 这个链接可能是同一个terminal,但我不确定。 我没有testing任何其他terminal仿真器。


更新:因为我不得不刮起痒,我用相同的脚本和全屏(1920×1200)testing了一大堆其他terminal仿真器。 我手动收集的统计信息在这里:

 wterm 0.3s
 0.3秒
 rxvt 0.3s
 mrxvt 0.4s
 konsole 0.6s
 yakuake 0.7s
 lxterminal 7s
 xterm 9s
侏儒terminal12s
 xfce4-terminal12s
 Valaterminal18s
 xvt 48s

logging的时间是手动收集的,但是非常一致。 我logging了最好的(ish)值。 YMMV,显然。

作为奖励,这是一个有趣的游览一些可用的各种terminal模拟器! 我很惊讶我的第一个“替代”testing竟然是最好的一堆。

你的redirect可能不会做什么,因为程序可以确定他们的输出FD是否指向一个tty。

指向terminal时,stdout可能会被缓冲(与C的stdoutstream行为相同)。

作为一个有趣的实验,尝试pipe道输出到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 

我不懂技术细节,因为我不知道他们,但是这并不令我感到意外:terminal不是为打印大量这样的数据而devise的。 事实上,你甚至可以提供一个链接到一个负载的GUI的东西,它必须做的每次你想打印的东西! 注意,如果你用pythonw来调用脚本,它不需要15秒; 这完全是一个GUI问题。 将stdoutredirect到一个文件以避免这种情况:

 import contextlib, io @contextlib.contextmanager def redirect_stdout(stream): import sys sys.stdout = stream yield sys.stdout = sys.__stdout__ output = io.StringIO with redirect_stdout(output): ... 

打印到terminal将会很慢。 不幸的是写一个新的terminal实现,我真的不知道如何显着加快这一点。

除了输出可能默认为线路缓冲模式以外,输出到terminal也会导致数据stream入具有最大吞吐量的terminal和串行线路,或伪terminal和处理显示的单独进程事件循环,从某种字体渲染字符,移动显示位来实现滚动显示。 后一种情况可能分散在多个进程(例如,远程login服务器/客户端,terminal应用程序,X11显示服务器),因此也存在上下文切换和延迟问题。