Когда задумал сделать парсер тИЦ для LJ Add/Remove Fiends — решил для начала проверить, какой способ загрузки страниц с показателями тИЦ из скрипта окажется быстрее, поскольку это в любом случае окажется самым узким местом в производительности парсера (любая обработка уже полученных данных, если она сделана без грубых ошибок типа бесконечного цикла, происходит гораздо быстрее загрузки этих данных по сети).
Для начала проверил на локальной машине (первый тест), затем на сервере (второй тест). Воспользовался модулем Benchmark::Timer
, по сути он ставит метки начала и конца, после чего вычисляет разницу между ними (то же самое несложно получить с использованием Time::HiRes
). У Benchmark::Timer
правда есть дополнительная возможность — он может гонять тест до получения результата с заданной обеспеченностью и заданным процентом ошибки.
Третьим тестом, уже из чистого любопытства (поскольку результат был предсказуем) был тест многопоточной загрузки. Естественно этот вариант оказался быстрее любого из однопоточных, в среднем в два раза (при 15 потоках).
Выводы — библиотеки Perl не только выгодней с точки зрения безопасности, но и работают стабильно быстрее вызовов системных утилит. Самую медленную загрузку показал Wget, основывать парсер на нём — явно плохая идея. Многопточность естественно вне конкуренции. Подробности ниже.
Объекты и условия тестирования
- Модули Perl:
- LWP::UserAgent 6.02
- LWP::Curl 0.07-1
- WWW::Curl::Easy 4.15
- LWP::Parallel::UserAgent 2.57 (15 потоков)
- Внешние загрузчики:
- Wget 1.12
- Curl 7.21.3
На локальной машине каждым способом загружалось по 200 страниц, с сервера — 100. Тесты выполнял по 5 раз, результаты усреднил (меня интересовали отношения результатов между собой, а не абсолютные значения). Ссылки передавались скрипту из файла, содержащего запрос тИЦ для 400 различных адресов, например:
http://bar-navig.yandex.ru/u?ver=2&show=32&url=http://nadonenado.livejournal.com
Результаты тестирования скорости загрузки страниц в Perl
200 запросов с локальной машины: tail -n 200 www-get-speed-data.txt | perl www-get-speed.pl |
|||
---|---|---|---|
Trails: 200 |
LWP-UA | 9.523s total | 47.615 ms/trial |
LWP-Curl | 8.500s total | 42.499 ms/trial | |
Wget 1.12 qx{} | 15.392s total | 76.958 ms/trial | |
WWW-Curl-Easy | 9.081s total | 45.405 ms/trial | |
Curl 7.21.3 qx{} | 13.340s total | 66.698 ms/trial | |
100 запросов с сервера (США): tail -n 100 www-get-speed-data.txt | perl www-get-speed.pl |
|||
Trails: 100 |
LWP-UA | 42.861s total | 428.611 ms/trial |
LWP-Curl | 41.762s total | 417.615 ms/trial | |
Wget 1.10.2 qx{} | 46.554s total | 465.543 ms/trial | |
WWW-Curl-Easy | 40.748s total | 407.476 ms/trial | |
Curl 7.20.0 qx{} | 45.750s total), | 457.500 ms/trial | |
Параллельные запросы с сервера (100, по 15 одновременных): tail -n 100 www-get-speed-data.txt | perl lwp-parallel-ua.pl |
|||
100 trial | LWP-PUA | 22.967s total | 229,670 ms/trial |
Код тестов для проверки скорости выгрузки страниц на Perl
Загрузка страниц из Perl в один поток (LWP, CURL, Wget)
[cc lang=»perl» escaped=»true»]
#!/usr/bin/perl
use warnings;
use strict;
use utf8;
use Benchmark::Timer;
# проверим производительность разных модулей
use LWP::UserAgent;
use LWP::Curl;
use WWW::Curl::Easy;
# + wget, curl через системный вызов
my $agent = q{‘Mozilla/5.0 (Windows; Windows NT 6.1; rv:2.0) Gecko’};
my $wget = ‘/usr/bin/wget -q -O /dev/null’.» —user-agent=$agent «;
my $curl = ‘/usr/bin/curl —silent —output /dev/null’.» —user-agent $agent «;
# тестовые ссылки получаем из станд. ввода
my @tst_url = <STDIN>;
my $timer = Benchmark::Timer->new(skip => 0);
my $lwp_ua = set_lwp_ua();
my $curl_ua = set_curl_ua();
my $lwp_curl = set_lwp_curl_ua();
foreach (@tst_url) {
chomp;
tr/_ /-/;
$timer->start(‘LWP-UA’);
$lwp_ua->get($_);
$timer->stop(‘LWP-UA’);
}
print $timer->report;
foreach (@tst_url) {
chomp;
tr/_ /-/;
$timer->start(‘LWP-Curl’);
$lwp_curl->get($_);
$timer->stop(‘LWP-Curl’);
}
print $timer->report;
foreach (@tst_url) {
chomp;
tr/_ /-/;
$timer->start(‘Wget 1.12 system’);
qx{$wget $_};
$timer->stop(‘Wget 1.12 system’);
}
print $timer->report;
foreach (@tst_url) {
chomp;
tr/_ /-/;
$timer->start(‘WWW-Curl-Easy’);
$curl_ua->setopt(CURLOPT_URL, $_);
$curl_ua->perform;
$timer->stop(‘WWW-Curl-Easy’);
}
print $timer->report;
foreach (@tst_url) {
chomp;
tr/_ /-/;
$timer->start(‘Curl system’);
qx{$curl $_};
$timer->stop(‘Curl system’);
}
print $timer->report;
exit 0;
sub set_lwp_curl_ua {
my $ua = LWP::Curl->new(
headers => 1, # возвращать заголовки
user_agent => $agent,
followlocation => 0, # отключить обработку редиректов
auto_encode => 1, # url encode
);
return $ua;
}
sub set_curl_ua {
my $ua = WWW::Curl::Easy->new;
$ua->setopt(CURLOPT_HEADER, 1); # возвращать ответ вместе с заголовком
$ua->setopt(CURLOPT_USERAGENT, $agent);
#$ua->setopt(CURLOPT_AUTOREFERER, 1);
$ua->setopt(CURLOPT_CRLF, 1);
$ua->setopt(CURLOPT_ENCODING, ‘deflate, gzip, identity’);
my $null = ‘/dev/null’;
$ua->setopt(CURLOPT_WRITEDATA, \$null);
return $ua;
}
sub set_lwp_ua {
my $ua = LWP::UserAgent->new;
$ua->agent($agent);
$ua->default_header(
‘Accept’ => ‘text/html, application/xml;q=0.9, application/xhtml+xml;q=0.1’,
‘Accept-Charset’ => ‘utf-8; *;q=0.1’,
‘Accept-Language’ => ‘ru,en-us;q=0.7,en;q=0.3’,
‘Accept-Encoding’ => ‘deflate, gzip, x-gzip, identity, *;q=0’,
);
return $ua;
}
[/cc]
Многопоточная загрузка страниц из Perl (15 потоков)
[cc lang=»perl» escaped=»true» nowrap=»true»]
#!/usr/bin/perl
use warnings;
use strict;
use utf8;
use Benchmark::Timer;
use HTTP::Request;
# проверим производительность разных модулей
use LWP::Parallel::UserAgent;
my $agent = q{‘Mozilla/5.0 (Windows; Windows NT 6.1; rv:2.0) Gecko’};
# тестовые ссылки получаем из станд. ввода
my @tst_url = <STDIN>;
my $timer = Benchmark::Timer->new(skip => 0,
#confidence => 97.5,
#error => 2,
);
my $pua = LWP::Parallel::UserAgent->new();
$pua->agent($agent);
$pua->default_header(
‘Accept’ => ‘text/html, application/xml;q=0.9, application/xhtml+xml;q=0.1’,
‘Accept-Charset’ => ‘utf-8; *;q=0.1’,
‘Accept-Language’ => ‘ru,en-us;q=0.7,en;q=0.3’,
‘Accept-Encoding’ => ‘deflate, gzip, x-gzip, identity, *;q=0’,
);
$pua->in_order (1); # handle requests in order of registration
$pua->duplicates(0); # ignore duplicates
$pua->timeout (2); # in seconds
$pua->redirect (0); # follow redirects
$pua->max_req (15); # max parallel requests per server (def 5)
$timer->start(‘LWP-PUA’);
foreach ( @tst_url ) {
shift;
chomp;
tr/_ /-/;
my $req = HTTP::Request->new(‘GET’, $_);
if ( my $res = $pua->register($req) ) {
print STDERR $res->error_as_HTML;
}
}
# отправка запросов в указанном числе потоков,
# аргумент wait задаёт макс. время ожидания ответа
my $entries = $pua->wait();
$timer->stop(‘LWP-PUA’);
print $timer->report;
exit 0;
[/cc]
Меня сильно беспокоят ваши результаты, особенно касающиеся wget. По идее при загрузке намного важнее скорость соединения, а не используемая качалка. Возможное объяснение — долгая инициализация wget’а при каждом запуске. Возможно, следовало передать файл с url и использовать ключ -i.
Может на «ты»? Я-то у тебя не на «вы» обращаюсь 🙂
Тогда условия не будут единообразны для всех. Меня интересовала именно загрузка URL, переданных из скрипта.
По поводу на «вы» — не обращайте внимания. Это привычка со студеньческих времен, когда все в инете казались взрослыми бородатыми дядьками 🙂
По поводу одинаковых условий — не согласен. LWP ведь у вас висит в памяти и уже инициализирован, а wget каждый раз по новой стартует. Чем больше запросов будете делать, тем сильннее вгет будет отставать.
Есть предложения по уточнению теста? Как обеспечить им всем одинаковые условия? Просто на данный момент ситуация такова — требовалось выяснить, что в конкретных условиях позволит быстрее загружать ссылки. Без дополнительных ухищрений, штатными средствами — результат получен.
Если есть способ ускорить wget — я буду только рад его узнать (но с сохранением вводной — получать ссылки так, как это происходит сейчас).