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

Я пытаюсь загрузить более 1 млн страниц (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-адреса в файл и делаю это в оболочке:

cat list | xargs curl

Я прочитал все 8000 страниц за две минуты.

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

Я пытался:

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

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

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

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

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

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

Любые подсказки приветствуются.


person Stiivi    schedule 18.05.2010    source источник
comment
Я попытался создать простой системный вызов curl вместо Curl::Easy: result = system(curl, -s, -o, path, url) и, кажется, это намного быстрее. Я получаю около 300 КБ/с вместо 60 КБ/с с Curl::Easy. Странно - системный вызов с огромными системными накладными расходами и без повторного использования соединения намного быстрее, чем библиотечные функции. Очевидно, что загрузка процессора намного выше, однако скорость намного лучше. В любом случае, это все еще не очень хорошее решение моей проблемы, хотя работает лучше.   -  person Stiivi    schedule 18.05.2010
comment
Вы пытались использовать более поздние версии curb с интерфейсом Curl:: Multi.download?   -  person todd    schedule 10.02.2011
comment
cat list | xargs curl передает много URL-адресов Curl в командной строке, а не по одному, поэтому Curl извлекает много URL-адресов одновременно. Вы можете сделать это в Ruby достаточно легко, но вам нужно сравнивать яблоки с яблоками и использовать HTTPClient или Typhoeus.   -  person the Tin Man    schedule 08.09.2012


Ответы (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

Если подумать, у вас могут возникнуть проблемы с памятью из-за огромного количества файлов. Один из способов предотвратить это — никогда не сохранять данные в переменной, а вместо этого передавать их напрямую в файл. Для этого вы можете использовать http://github.com/igrigorik/em-http-request.

EventMachine.run {
  http = EventMachine::HttpRequest.new('http://www.website.com/').get
  http.stream { |chunk| print chunk }
  # ...
}
person Jonas Elfström    schedule 18.05.2010
comment
Аккуратно, кажется, работает очень хорошо! :-) Я постараюсь протестировать его ночью (чтобы не заглушить один из правительственных сайтов) хотя бы на 50-100 тыс. файлов, чтобы увидеть, сколько времени это займет, и выявить возможные проблемы. Его нужно периодически запускать позже. Спасибо за совет. - person Stiivi; 18.05.2010

Итак, если вы не установите обработчик on_body, то curb будет буферизовать загрузку. Если вы загружаете файлы, вы должны использовать обработчик 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

person todd    schedule 18.05.2010

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

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

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

person Henley    schedule 11.01.2013
comment
добавить ссылку для HTTPClient? - person jwfearn; 01.10.2013

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

Что я знаю точно, так это то, что Ruby — это интерпретируемый язык; неудивительно, что он медленнее, чем сильно оптимизированный код, скомпилированный для конкретной платформы. Каждая файловая операция, вероятно, будет иметь проверки, которых нет в curl. «Некоторые другие вещи» еще больше замедлят работу.

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

person Alphax    schedule 18.05.2010
comment
Методы класса Ruby Curl::Easy являются обертками функций libcurl C, весь загружаемый код написан на C. Дополнительные вызовы и накладные расходы интерпретатора в этом случае незначительны. Также обратите внимание, что выполнение командной строки curl было быстрее и включает создание процесса и другие вещи (что быстро, но я считаю, что это медленнее, чем вызов функции интерпретатора Rubu). - person Stiivi; 18.05.2010
comment
@Stiivi, очевидно, какое-то ваше предположение где-то неверно. Вероятно, эта часть: дополнительный вызов и накладные расходы на интерпретатор незначительны. Что-то не маловажно. Вероятно, взяли строки C и превратили их в строки Ruby. - person tster; 18.05.2010

Стииви,

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

person Vojto    schedule 18.05.2010

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

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

person Chris D    schedule 15.01.2012