Почему curl в Ruby медленнее, чем curl командной строки?

Я пытаюсь загрузить более 1m страниц (url-адреса, заканчивающиеся идентификатором последовательности). Я реализовал своего рода многоцелевой менеджер загрузки с настраиваемым количеством потоков загрузки и одним потоком обработки. Загрузчик загружает файлы пакетами:

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 страниц. При использовании кода выше я получаю 1000 за 2 минуты. Когда я пишу все URL-адреса в файл и делаю в shell:

cat list | xargs curl

Я ген все 8000 страниц в два протокол.

дело в том, что мне нужно, чтобы он был в ruby-коде, потому что есть другой код мониторинга и обработки.

Я пробовал:

  • Curl:: Multi-это как-то быстрее, но пропускает 50-90% файлов (не загружает их и не дает повода/кода)
  • несколько потоков с завитком:: легко-примерно с той же скоростью, что и однопоточный

почему повторно используется Curl:: Easy медленнее, чем последующие вызовы curl командной строки и как могу я сделать это быстрее? Или что я делаю не так?

Я бы предпочел исправить свой код диспетчера загрузки, чем сделать загрузку для этого случая по-другому.

до этого я вызывал wget командной строки, который я предоставил файл со списком URL-адресов. Howerver, не все ошибки были обработаны, также не удалось указать выходной файл для каждого URL отдельно при использовании списка URL.

теперь мне кажется, что лучшим способом было бы использовать несколько потоки с системным вызовом команды "curl". Но почему, когда я могу использовать непосредственно Curl в Ruby?

код для менеджера скачать здесь, если это может помочь: Скачать (я играл с тайм-аутами, от не-установки его на различные значения, это, казалось, не помогло)

какие-то намеки оценили.

6 ответов


Это может быть подходящей задачей для 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

подумайте об этом, вы могли бы получить проблемы с памятью из-за огромного количества файлов. Один из способов предотвратить это-никогда не хранить данные в переменной, а вместо этого напрямую передавать их в файл. Вы могли бы использовать em-http-запрос для этого.

EventMachine.run {
  http = EventMachine::HttpRequest.new('http://www.website.com/').get
  http.stream { |chunk| print chunk }
  # ...
}

Итак, если вы не установите обработчик on_body, curb будет буферизировать загрузку. Если вы загружаете файлы, вы должны использовать обработчик on_body. Если вы хотите загрузить несколько файлов с помощью Ruby Curl, попробуйте Curl:: Multi.скачать интерфейс.

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


были сделаны тесты, которые сравнивали curb с другими методами, такими как HTTPClient. Победителем почти во всех категориях стал HTTPClient. Кроме того, были некоторые документированные сценарии, где curb не работает в многопоточных сценариях.

Как и у вас, у меня был ваш опыт. Я запускал системные команды curl в 20 + параллельных потоках, и это было 10 x быстрее, чем запуск curb в 20+ параллельных потоках. Что бы я ни делал, так было всегда.

с тех пор я переключился на HTTPClient, и разница огромная. Теперь он работает так же быстро, как 20 параллельных системных команд curl, и использует меньше процессора.


прежде всего позвольте мне сказать, что я почти ничего не знаю о Ruby.

Я знаю, что Ruby-это интерпретируемый язык; неудивительно, что он медленнее, чем сильно оптимизированный код, который был скомпилирован для определенной платформы. Каждая файловая операция, вероятно, будет иметь проверки вокруг него, что curl не делает. "Кое-что другое" Еще больше замедлит процесс.

вы пробовали профилировать свой код, чтобы увидеть, где тратится большая часть времени?


Stiivi,

есть шанс, что Net:: HTTP было бы достаточно для простой загрузки HTML-страниц?


вы не указали версию Ruby, но потоки в 1.8.x-это потоки пользовательского пространства, не запланированные ОС, поэтому весь интерпретатор Ruby использует только один процессор/ядро. Кроме того, существует глобальная блокировка интерпретатора и, возможно, другие блокировки, препятствующие параллелизму. Поскольку вы пытаетесь увеличить пропускную способность сети, вы, наверное, справляться с ЦП.

создайте столько процессов, сколько у машины есть памяти, и ограничьте зависимость от потоков.