2016-12-10 7 views
3

短いバージョン: Rubyを使用して高性能を維持しながらSTDIN(またはファイル)charからcharを読み取る方法は? RubyのI/Oパフォーマンス - charによるファイルcharの読み取り

(問題は、おそらく特定のルビーではありませんが)ロングバージョン: 私は、パイプで連結されたテキストデータから読み取る見つけ、それに番号を収集し、いくつかの処理を行う必要があります小さなユーティリティを設計していますルビーを学びながら。

cat huge_text_file.txt | program.rb 

input > 123123sdas234sdsd5a ... 
output > 123123, 234, 5, ... 

私はについての私の懸念を持っていたのに(テキスト入力は、巨大な(ギガバイト)であるかもしれないし、それが改行や空白文字が含まれていない可能性があります(任意の数字以外の文字が区切りである)ので、私は読んcharで文字を行いましたパフォーマンス)、それはこのようにすることは信じられないほど遅いことがわかります。

900KBの入力ファイルでcharをcharで読み込むだけで約7秒かかります!

while c = STDIN.read(1) 
end 

改行でデータを入力して1行ずつ読み込むと、同じファイルが100倍高速に読み込まれます。

while s = STDIN.gets 
end 

それはSTDIN.read(1)でパイプから読み取りが任意のバッファリングと、読み取りが起こるたびに関与しないように、ハードドライブがヒットしたようだ - しかし、それはOSによってキャッシュされるべきではないのですか?

STDIN.gets charが '\n'になるまで内部的にcharを読み込みませんか?

Cを使用すると、バッファウィンドウで分割された数値を処理する必要がありますが、Ruby用の洗練されたソリューションのようには見えません。では、これを行う正しい方法は何ですか? Pythonで同じファイルを読み込む

P.Sタイミングは:

for line in f: 
    line 
f.close() 

時間を実行すると、0.01秒です。

c = f.read(1) 
while c: 
    c = f.read(1) 
f.close() 

実行時間は0.17秒です。

ありがとうございます!

+0

ここで問題と思われるのはIOだけでなくガベージコレクタです。 'read(1)'を使用すると、ファイルの各バイトに新しいStringオブジェクトが作成されます。 temp文字列を作成し、 'read'を呼び出すたびにそれを再利用すると、より良い結果が得られるかもしれません。したがって、最初に 'buffer =" "'を実行すると、あなたのループ内のreadを 'STDIN.read(1、buffer)'として呼び出すことができます。 – matt

+0

@mattの提案に感謝します!私はそれを試みました、それは少し速いです。 'cat'からパイプされた900Kファイルで、' buffer = ''の 'STDIN.read(1)'の実行時間は平均4.6秒です。 STDIN.read(1、buffer) ' - STDIN.getsのための4.5秒 - 0.08秒。私はSSDからHDDにすべてのものを移動してみました。私は、入力ファイルはOSによってキャッシュされなければならないと思っています。 – epsylon

+0

私はPythonで同じものを計時し、 'for line in file:line'は0.01秒です。while while c:c = file.read(1)'は0.17秒です。しかしチャンクで読むのはまだ10倍以上高速です。 – epsylon

答えて

3

このスクリプトは、単語単位でIOオブジェクトを読み取り、1000語が見つかるたびに、またはファイルの終わりに達するたびにブロックを実行します。

同時に1000ワードを超えるメモリが保持されません。セパレータとして" "を使用すると、「単語」に改行が含まれる可能性があることに注意してください。

このスクリプトは、ファイル全体の内容上の任意の操作を行うことを避けるために、lazy(言葉のEnumeratorを取得するには、この場合は空白)とeach_sliceがBATCH_SIZE単語の配列を取得するためのセパレータを指定するIO#eachを使用しています。代わりに猫と|を使用しての

batch_size = 1000 

STDIN.each(" ").lazy.each_slice(batch_size) do |batch| 
    # batch is an Array of batch_size words 
end 

、あなたはまた、ファイルを直接読み込むことができます:このコードで

batch_size = 1000 

File.open('huge_text_file.txt').each(" ").lazy.each_slice(batch_size) do |batch| 
    # batch is an Array of batch_size words 
end 

を、何の数は分割されず、何のロジックが必要とされていない、それはよりもはるかに高速でなければなりませんファイルcharをcharで読み込み、ファイル全体を文字列に読み込むよりもはるかに少ないメモリを使用します。

+0

ありがとう!それはより高速ですが、実際には 'gets 'とほぼ同じですが、' \ n'の代わりに空白を区切りとして使用します。この問題では、セパレータは数字以外の何ものでもなく、時折空白または改行の間の文字列が巨大になる可能性がありますので、メモリが不足する可能性があります。何とかそれをすべて読まずにテキストを自動的に正規表現する方法はありますか? :) '.each(" ")'や '.gets'はどうしますか?ブロックを読んだり、セパレータの後で分割して廃棄したりしますか? P.P.パイプは柔軟性のために使用されているので、コマンドを連鎖させたり、curlから入力を得ることができます。 – epsylon

+0

次に、固定長のチャンクを読み込み、番号を分割するかどうかを確認する必要があります。 –

+0

'each(" \ n ")each_slice(batch_size)を使っているように見えるのは、実際には' f.each_line'より少し速いですが、 '.gets'を使うときと比べると半分遅いです。中間オブジェクトが作成されているからだと思います。 – epsylon

関連する問題