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

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

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

我写了一个脚本('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 转储到垃圾中一样快?

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),但不会改变我的问题。它只是添加到我的问题,因为我不明白为什么终端屏幕渲染应该减慢应用程序写入标准输出的速度。为什么我的程序需要等待屏幕渲染才能继续?

不是所有终端/tty 应用程序都是平等的吗?我还没有做实验。在我看来,终端应该能够缓冲所有传入的数据,不可见地解析/渲染它,并且仅以合理的帧速率渲染当前屏幕配置中可见的最新 block 。因此,如果我可以在 ~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

最佳答案

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 缓冲的重要性。 :-)

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

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

为了使比较公平,您必须使文件 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,但在刷新缓冲区之前您不会看到写入终端的内容。这是一种权衡:交互性与批量效率。)

https://stackoverflow.com/questions/3857052/

相关文章:

python - 如何在 Python 3.x 和 Python 2.x 中使用 pip

python - 为什么我不能在 python 中创建一个轮子?

linux - 如何在 Bash 中给定超时后杀死子进程?

linux - 从 URL 执行 bash 脚本

python - 为什么在 Pylint 认为不正确的条件值中使用 len(SEQUENCE)?

linux - 在远程 Linux 机器上编译 C++ - "clock skew detected

python - 在 Python 中获取大文件的 MD5 哈希

python - 为什么使用 Python 的 os 模块方法而不是直接执行 shell 命令?

python - Django Admin - 更改标题 'Django administratio

python - 如何将 Pandas 数据添加到现有的 csv 文件中?