首页 文章

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

提问于
浏览
146

我一直对使用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

哇 . 为了确保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

所以这不是一个蟒蛇技巧,它只是终端 . 我总是知道将输出转储到/ dev / null加速了,但从来没有认为它是那么重要!

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

This link讨论了终端如何阻止I / O以便它可以"parse [the input], update its frame buffer, communicate with the X server in order to scroll the window and so on" ......但是我没有完全理解它 . 什么可以服用这么久?

我希望没有出路(缺少更快的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

6 回答

  • 13

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

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

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

    另一方面,终端几乎不进行缓冲:每个人等待 full / write(line) 等待 full 写入(即显示到输出设备)完成 .

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

    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行 .

    但是,通过上面对无缓冲写入的修改,只需要40秒即可将1000行写入磁盘 . 我放弃了等待100,000行写入,但从前一次推断,需要 over an hour .

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

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

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

  • 2

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

    Question 1: Why is printing to stdout slow?

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

    Question 2: Can it be sped up?

    Answer: 是的它可以,但似乎不是来自计划方面(侧面做'printing'到stdout) . 要加快速度,请使用速度更快的终端仿真程序 .

    说明...

    我尝试了一个名为 wterm 的自编'lightweight'终端程序,并获得了明显更好的结果 . 下面是我的测试脚本的输出(在问题的底部)在 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.26s比12s好多了!我不知道 wterm 是否更加智能地呈现了如何根据我的建议(以合理的帧速率渲染'visible'尾部)进行筛选,或者它是否只是"does less"而不是 gnome-terminal . 出于我的问题的目的,我得到了答案 . gnome-terminal 很慢 .

    所以 - 如果你有一个长时间运行的脚本,你感觉很慢,它会向stdout发送大量的文本...尝试一个不同的终端,看看它是否更好!

    请注意,我几乎随机从ubuntu / debian存储库中删除 wterm . This 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
    

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

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

  • 4

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

    它's likely that stdout is line buffered when pointing to a terminal (the same as C' s 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
    
  • 139

    我可以't talk about the technical details because I don't不知道它们,但这并不让我感到惊讶:终端不是设计用于打印这样的大量数据 . 实际上,你甚至提供了一个链接到一堆GUI的东西,它每次你想要打印时都必须做!请注意,如果您使用 pythonw 调用脚本,则不需要15秒;这完全是一个GUI问题 . 将 stdout 重定向到文件以避免这种情况:

    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):
        ...
    
  • 79

    打印到终端的速度会很慢 . 不幸的是,由于没有编写新的终端实现,我无法真正看到你如何显着提高速度 .

  • 2

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

相关问题