RubyのCurlがコマンドラインカールよりも遅いのはなぜですか?
質問
1m以上のページをダウンロードしようとしています(シーケンスIDで終了するURL)。設定可能な数のダウンロードスレッドと1つの処理スレッドを備えた多目的ダウンロードマネージャーの種類を実装しました。ダウンローダーはバッチでファイルをダウンロードします:
curl = Curl::Easy.new
batch_urls.each { |url_info|
curl.url = url_info[:url]
curl.perform
file = File.new(url_info[:file], "wb")
file << curl.body_str
file.close
# ... some other stuff
}
8000ページのサンプルをダウンロードしようとしました。上記のコードを使用すると、2分で1000を取得します。すべてのURLをファイルに書き込み、シェルで実行するとき:
cat list | xargs curl
私は2分で8000ページすべてを生成します。
他にも監視および処理コードがあるため、Rubyコードでそれを持っている必要があります。
私が試してみました:
- curl :: multi-それはなんとか速いですが、ファイルの50〜90%を逃します(それらをダウンロードせず、理由/コードを与えません)
- カールを備えた複数のスレッド:: easy-シングルスレッドと同じ速度
再利用されたカール::後続のコマンドラインカールコールよりも簡単なのはなぜですか?または私が間違っていることは何ですか?
このケースのダウンロードを別の方法で作成するよりも、ダウンロードマネージャーコードを修正したいと思います。
この前に、私はURLのリストを含むファイルを提供したコマンドラインWGETを呼び出していました。 Howerver、すべてのエラーが処理されたわけではなく、URLリストを使用するときに各URLの出力ファイルを個別に指定することもできませんでした。
今では、システムコールを「curl」コマンドで複数のスレッドを使用することが最善の方法であるように思えます。しかし、なぜRubyで直接Curlを使用できるのですか?
ダウンロードマネージャーのコードはここにあります。 ダウンロードマネジャー (私はタイムアウトでプレイしましたが、それをさまざまな値までに設定してから、それは助けにはなりませんでした)
ヒントは高く評価されています。
解決
これは適切な作業になる可能性があります Typhoeus
このようなもの(テストされていない):
require 'typhoeus'
def write_file(filename, data)
file = File.new(filename, "wb")
file.write(data)
file.close
# ... some other stuff
end
hydra = Typhoeus::Hydra.new(:max_concurrency => 20)
batch_urls.each do |url_info|
req = Typhoeus::Request.new(url_info[:url])
req.on_complete do |response|
write_file(url_info[:file], response.body)
end
hydra.queue req
end
hydra.run
考えてみると、ファイルが非常に大きいため、メモリの問題が発生する可能性があります。それを防ぐ1つの方法は、データを変数に決して保存せず、代わりにファイルに直接ストリーミングすることです。使用できます em-http-request そのために。
EventMachine.run {
http = EventMachine::HttpRequest.new('http://www.website.com/').get
http.stream { |chunk| print chunk }
# ...
}
他のヒント
したがって、curbがダウンロードを緩衝するよりもon_bodyハンドラーを設定しない場合。ファイルをダウンロードする場合は、ON_BODYハンドラーを使用する必要があります。 Ruby Curlを使用して複数のファイルをダウンロードする場合は、Curl :: Multi.DownLoadインターフェイスを試してください。
require 'rubygems'
require 'curb'
urls_to_download = [
'http://www.google.com/',
'http://www.yahoo.com/',
'http://www.cnn.com/',
'http://www.espn.com/'
]
path_to_files = [
'google.com.html',
'yahoo.com.html',
'cnn.com.html',
'espn.com.html'
]
Curl::Multi.download(urls_to_download, {:follow_location => true}, {}, path_to_files) {|c,p|}
単一のファイルをダウンロードするだけの場合。
Curl::Easy.download('http://www.yahoo.com/')
これが良いリソースです: http://gist.github.com/405779
縁石とhttpclientなどの他の方法を比較したベンチマークが行われました。ほぼすべてのカテゴリで勝者はhttpclientでした。さらに、マルチスレッドシナリオで縁石が機能しないいくつかの文書化されたシナリオがあります。
あなたのように、私はあなたの経験をしました。私は20以上の同時スレッドでカールのシステムコマンドを実行しましたが、20以上の並行スレッドで縁石を実行するよりも10 xファスターでした。関係なく、私が試したことで、これは常にそうでした。
それ以来、私はhttpclientに切り替えましたが、違いは巨大です。これで、20の同時Curlシステムコマンドと同じくらい速く実行され、CPUも少なくなります。
まず、ルビーについてほとんど何も知らないと言わせてください。
私が知っていることは、Rubyが解釈された言語であるということです。特定のプラットフォーム用にコンパイルされている重度に最適化されたコードよりも遅いことは驚くことではありません。すべてのファイル操作にはおそらくその周りにチェックがあります curl
そうではありません。 「他のいくつかのもの」は、物事をさらに遅くするでしょう。
ほとんどの時間がどこに費やされているかを確認するためにコードのプロファイリングを試みましたか?
stiivi、
チャンス net :: http HTMLページを簡単にダウンロードするには十分でしょうか?
Rubyバージョンを指定しませんでしたが、1.8.xのスレッドはOSでスケジュールされていないユーザー空間スレッドであるため、Rubyインタープリター全体が1つのCPU/Coreのみを使用します。それに加えて、グローバルなインタープリターロックがあり、おそらく他のロックも同様に同時性を妨げています。ネットワークスループットを最大化しようとしているので、おそらくCPUを十分に活用していません。
マシンのメモリと同じくらい多くのプロセスを生み出し、スレッドへの依存を制限します。