193

私はいつも、printステートメントで端末に出力するのにかかる時間に驚かされたりイライラしたりしてきました。最近の痛々しいほど遅いロギングの後、私はそれを調べることにしました、そして、費やされたほとんどすべての時間が端末が結果を処理するのを待っているのを見つけて非常に驚きました。

stdoutへの書き込みをなんとかスピードアップできますか?

print_timer.pystdout、ファイル、およびstdoutにリダイレクトされた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

わお。stdoutを/dev/ nullなどに再割り当てしたことを認識するなど、Pythonが舞台裏で何かを行っていないことを確認するために、スクリプトの外部でリダイレクトを行いました...

$ 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を使用してガベージにダンプするのと同じくらい効果的に高速であるのはどうしてですか?

このリンクでは、端末が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アプリが同じように作成されていませんか?私はまだ実験していません。端末はすべての着信データをバッファリングし、それを目に見えない形で解析/レンダリングし、現在の画面構成に表示されている最新のチャンクのみを適切なフレームレートでレンダリングできる必要があるように思えます。したがって、ディスクへの書き込み+ fsyncを約0.1秒で実行できる場合、端末は同じ操作をその順序で完了できるはずです(実行中に画面が数回更新される可能性があります)。

プログラマーにとってこの動作を改善するために、アプリケーション側から変更できる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
4

6 に答える 6

179

物理ディスクへの書き込みが「画面」(おそらくすべてRAM操作)への書き込みよりもはるかに高速であり、/ dev / nullを使用してガベージにダンプするのと同じくらい効果的に高速であるのはどうしてですか?

おめでとうございます。I/Oバッファリングの重要性を発見しました。:-)

ディスクは高度にバッファリングされているため、高速であるように見えますwrite()。実際に物理ディスクに書き込まれる前に、すべてのPythonの呼び出しが返されます。(OSは後でこれを行い、何千もの個別の書き込みを大きくて効率的なチャンクに結合します。)

一方、端末はバッファリングをほとんどまたはまったく行いません。各個人は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

私は自分のマシンでファイル書き込みテストを実行しましたが、バッファリングを使用すると、ここでも100,000行で0.05秒になります。

ただし、バッファなしで書き込むための上記の変更では、ディスクに1,000行しか書き込むのに40秒かかります。100,000行が書き込まれるのを待つのをあきらめましたが、前の行から外挿すると、1時間以上かかります。

これで、端末の11秒が見えてきますね。

したがって、元の質問に答えるために、端末への書き込みは実際には非常に高速であり、すべてが考慮されており、それをはるかに高速化する余地はあまりありません(ただし、個々の端末は、実行する作業量が異なります。これに対するRussのコメントを参照してください。答え)。

(ディスクI / Oのように、書き込みバッファリングを追加することもできますが、バッファがフラッシュされるまで、端末に書き込まれた内容は表示されません。これは、対話性とバルク効率のトレードオフです。)

于 2010-10-04T17:17:51.330 に答える
107

すべてのコメントをありがとう!私はあなたの助けを借りて自分でそれに答えることになりました。しかし、あなた自身の質問に答えるのは汚い感じがします。

質問1:stdoutへの印刷が遅いのはなぜですか?

回答: stdoutへの印刷は本質的に遅いわけではありません。遅いのはあなたが使っている端末です。また、アプリケーション側のI / Oバッファリング(例:Pythonファイルバッファリング)とはほとんど関係がありません。下記参照。

質問2:スピードアップできますか?

回答:はい、できますが、プログラム側(stdoutへの「印刷」を行う側)からではないようです。高速化するには、より高速な別のターミナルエミュレータを使用します。

説明...

と呼ばれる自己記述型の「軽量」ターミナルプログラムを試してみたところ、かなり良い結果wtermが得られました。以下は、基本的な印刷オプションがgnome-terminalを使用して12秒かかった同じシステムで1920x1200で実行したときの、私のテストスクリプト(質問の下部)の出力です。wterm

-----
タイミングの概要(各100k行)
-----
印刷:0.261秒
ファイルへの書き込み(+ fsync):0.110秒
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
ヤクアケ0.7s
lxterminal 7s
xterm 9s
gnome-terminal12s
xfce4-terminal 12s
バラターミナル18秒
xvt 48s

記録された時間は手動で収集されますが、かなり一貫しています。最高の(っぽい)値を記録しました。YMMV、明らかに。

ボーナスとして、そこにあるさまざまなターミナルエミュレーターのいくつかの興味深いツアーでした!私の最初の「代替」テストが最高のものであることが判明したことに驚いています。

于 2010-10-05T01:35:46.197 に答える
14

プログラムは出力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
于 2010-10-04T16:24:08.353 に答える
4

技術的な詳細はわからないので話せませんが、驚くことではありません。端末はこのような大量のデータを印刷するようには設計されていません。確かに、何かを印刷するたびに実行する必要のある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):
    ...
于 2010-10-04T16:25:39.490 に答える
3

端末への印刷が遅くなります。残念ながら、新しい端末の実装を作成することを除いて、これを大幅に高速化する方法がわかりません。

于 2010-10-04T16:20:19.110 に答える
2

おそらくデフォルトでラインバッファモードになっている出力に加えて、端末への出力は、データを最大スループットで端末とシリアル回線、または疑似端末とディスプレイを処理する別のプロセスに流します。イベントループ、一部のフォントからの文字のレンダリング、スクロール表示を実装するための表示ビットの移動。後者のシナリオは、おそらく複数のプロセス(Telnetサーバー/クライアント、ターミナルアプリ、X11ディスプレイサーバーなど)に分散しているため、コンテキストの切り替えと遅延の問題もあります。

于 2010-10-04T16:52:39.003 に答える