web-dev-qa-db-ja.com

なぜ標準出力への印刷が非常に遅いのですか?スピードアップできますか?

印刷文を使用して端末に単純に出力するのにどれだけの時間がかかるか、私はいつもびっくりしました。最近の痛々しいほど遅いロギングの後、私はそれを調べることにし、ほとんどall端末が処理するのに費やされた時間が待っていることに非常に驚いた結果。

Stdoutへの書き込みを何らかの方法で高速化できますか?

スクリプト(この質問の最後にある 'print_timer.py')を作成して、stdout、file、およびstdoutを/dev/nullにリダイレクトして、10万行を書き込むタイミングを比較しました。タイミングの結果は次のとおりです。

$ 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 op)への書き込みよりもはるかに速く、/ 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)が、私の質問は変わりません。ターミナル画面のレンダリングが標準出力へのアプリケーションの書き込みを遅くする理由がわからないので、それは私の質問にaddsだけです。プログラムが画面のレンダリングを続行するのを待つ必要があるのはなぜですか?

すべての端末/ 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
158
Russ

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

質問1:なぜ標準出力への印刷が遅いのですか?

Answer:stdoutへの印刷は本質的にnot遅くなります。遅いのは、使用している端末です。また、アプリケーション側のI/Oバッファリングとはほとんど関係ありません(例:pythonファイルバッファリング)。以下を参照してください。

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

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

説明...

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

 ----- 
タイミングの概要(各10万行)
 ----- 
 print:0.261 s 
ファイルへの書き込み( + fsync):0.110 s 
 print with stdout =/dev/null:0.050 s 

0.26秒は12秒よりもはるかに優れています! wtermが、私が提案した方法に沿ってスクリーンにレンダリングする方法が合理的か(合理的なフレームレートで「可視」テールをレンダリングするか)、それとも単に「少ない」かどうかはわかりません。 gnome-terminalより。しかし、私の質問の目的のために、私は答えを持っています。 gnome-terminalは遅いです。

だから-実行時間が長く、大量のテキストを標準出力に吐き出していると感じているスクリプトを実行している場合...別の端末を試して、それが良いかどうかを確認してください!

Ubuntu/debianリポジトリからwtermをほぼランダムにプルしたことに注意してください。 このリンク は同じ端末かもしれませんが、わかりません。他の端末エミュレーターはテストしませんでした。


更新:かゆみを掻く必要があったため、同じスクリプトとフルスクリーン(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 

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

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

84
Russ

物理ディスクへの書き込みは、「スクリーン」(おそらく全RAM op)への書き込みよりもはるかに速く、/ dev/nullを使用してガベージに単にダンプするのと同じくらい効果的です。

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

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

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

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

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

これにより、端末の11秒が遠近法になりますよね?

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

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

146
Pi Delport

プログラムは、出力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
13
Hasturkun

私はそれらを知らないので技術的な詳細について話すことはできませんが、これは私を驚かせません:端末はこのような多くのデータを印刷するために設計されていません。確かに、何かを印刷したいときに毎回やらなければならないたくさんの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):
    ...
4
Katriel

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

3
shuttle87

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

2
Liudvikas Bukys