Pull to refresh

Comments 36

Если Вам не сложно, сравните, пожалуйста с парсером HTML::TreeBuilder (Perl). Я пользуюсь исключительно им.
jsdom использует упомянутый htmlparser непосредственно для парсинга страницы, более того он на выходе дает полноценный DOM, а не AST. ИМХО, я бы его выкинул из сравнения, т.к. по сути это не совсем чистый парсер
Спасибо за пояснение. Тем не менее, кто-то jsdom использует и для простого парсинга — habrahabr.ru/qa/29711/
1. Емнип, сами парсеры разной сложности, из-за чего может быть разброс по памяти скорости и т.п. То есть если какой-то парсер в процессе еще жнет, шьет и на дуде играет, то… Но я не спец по этим парсерам, поэтому ничего не смогу сказать

2. Erlang порадовал, сам не ожидал, но, не глядя в код, можно сделать следующие предположения:
— файл считывается, как binary, после чего вычленение нужных участков становится во-первых легким (google: erlang bit syntax), а во-вторых эффективным (см. пункт 3)

3. Про «копирование всего и вся» и память
— из-за немутабельности данных сборщик мусора с одной стороны простой, с другой — эффективный. Никаких stop-the-world и прочего, неиспользованные данные убираются из памяти весьма оперативно
— Для большинства данных в пределах одного процесса копируется только указатель на структуру, содержащую данные, а сами данные упакованы весьма эффективно. При передаче из процесса в процесс данные копируются
— В случае двоичных данных больше определенного размера эти данные существуют в одном экземпляре на ноду (не процесс, а именно ноду), и повсюду передается только указатель на эти данные.
Поэтому потребление памяти может быть весьма маленьким по сравнению с другими участниками

4. Оверхед на запуск — есть такое, потому что запускается много придожений и подгружается много кода. Можно сделать минимальную систему, запускающую только полтора приложения и т.п., но в реальном приложении это не так критично, потому что VM запускается один раз, а потом работает до скончания времен

5. Есть еще sax-парсеры для Эрланга (как и для других языков), по идее они должны смочь обработать невалидный HTML
1. Да, всё верно. Например jsdom делает много дополнительной работы, а тот же mochiweb_html или cheerio строят простейшее дерево. Но в контексте бенчмарка это не важно. Цель как раз показать, что использовать слишком навороченные парсеры для простых задач не стоит.

2. Да, mochiweb_html парсит всё в бинарном виде, дерево тоже состоит из таплов и бинарей (листы только для списков атрибутов и дочерних тегов). В процессе парсинга в контексте передается текущая позиция парсера как сдвиг в исходном HTML binary, т.е. он делает примерно так
S=#decoder{offset=O}
...
<<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) ->
            tokenize_attributes(B, ?INC_CHAR(S, C), Acc);


3. Мне вот интересно, Erlang парсер в итоге строит структуру наподобие
{<<"html">>, [{<<"lang">>, <<"ru-RU">>}], [
    {<<"head">>, [], []},
    {<<"body">>, [], [<<"Hello, World!">>]}
]}
Интересно все вот эти мелкие бинари на самом деле являются ссылками на участки исходного большого бинари (бинари слайс это вроде называется), или создаются отдельные маленькие копии участков памяти?

4. Да, я упомянул оверхед на запуск мельком просто потому что были все нужные данные чтобы его посчитать, так это не особо интересная информация.

5. Да, SAX несомненно будет и быстрее и экономнее по памяти (mochiweb_html например в 2 этапа парсит — сперва строит чем-то вроде SAX парсера плоский список токенов, а на втором этапе генерирует из него вложенное дерево). Я хотел померять именно DOM парсеры.
> Интересно все вот эти мелкие бинари на самом деле являются ссылками на участки исходного большого бинари

Таки на участки исходного binary. См. erlanger.ru/ru/page/2431/erlang-binaries-i-sborka-musora-i-tyazhelyj-vzdoh

«Более того, Erlang проводит некоторые дополнительные оптимизации. При использовании erlang:split_binary/2 или при извлечении данных при помощи сопоставления с образцом результат не обязательно является новым binary. Вместо этого VM создает новый тип под названием sub-binary, который на деле является ссылкой на оригинальные данные — на heap binary или на refc-binary.»

Из-за этого, видать, потребление памяти относительно низкое — все лежит в одном большом двоичном куске и расходы только на указатели
Это здорово. Недавно наткнулся на другой бенчмарк XML парсеров, в котором с огромным отрывом побеждал парсер на языке D (бенчмарк пояснения). Так вот, он выигрывал как раз в основном за счет zero-copy — т.е. не копировал куски исходного HTML, а ссылался на них (array-slicing).
Но он не раскодировал XML entities, так что &lt; не превращается в <. Если бы не это допущение, то пришлось бы автору парсера придумывать какие-то хаки, чтобы оставаться zero-copy.
В эрланге это можно оптимизировать, используя io_lists наподобие [<<"бинари до entity">>, <<"<">>, <<"бинари после entity">>], хотя работать с таким уже менее удобно.
> Вот так сюрприз! PyPy всем сливает.
Не понимаю, что вас удивило — PyPy является интерпретатором Python написанный на Python, откуда там взяться производительности?
Я достаточно давно его пробовал, несколько лет назад там все было на Python, хорошо что добавили новых бэкендов.
Он не на python написан, а на RPython, это (синтаксически) подмножество python, но по сути Си-подобный язык (который, собственно, в си и транслируется).
Есть предположение, что тормоза и аппетиты нативных реализаций на python являются следствием immutable типов данных, которые копируются при каждом изменении. А ведь к ним, применимо к парсингу текста, относятся строки и кортежи.

И я рад за наличие у python такого качественного libxml2 биндинга.
> Есть предположение, что тормоза и аппетиты нативных реализаций на python являются следствием immutable типов данных, которые копируются при каждом изменении.

Это проблема невпихуемости невпихуемого :) То есть рантайм Питона не приспособлен к работе с такими данными и, видать, GC тупит.
Присылайте свои реализации! Это очень просто!
На гитхабе уже прислали реализации для ruby и golang. Обновляйте пост!
Доберусь до дома и запущу в том-же окружении. Спасибо!
Я там еще добавил реализацию на джаве на JSoup, не забывайте про нас :)
Ага, я видел. Только вы таймер стартуете до того, как файл считали, а в других парсерах таймер стоит непосредственно вокруг цикла. Я там в комментарии к пулл-реквесту написал.
Хотя может это и не так существенно.
К сожалению, пока не успеваю добавить на графики, но кому интересно — вот результаты для 100 итераций для perl, Go и Ruby
Скрытый текст
$ PLATFORMS="golang perl ruby" ./run.sh 100
===============
golang
===============
******************************
parser:./bin/bench_exp_html	file:../page_google.html
0.270294 s
real:0.27	user:0.27	sys:0.00	max RSS:4540
******************************
parser:./bin/bench_gokogiri	file:../page_google.html
0.315391 s
real:0.32	user:0.28	sys:0.03	max RSS:78196
******************************
parser:./bin/bench_h5	file:../page_google.html
5.436318 s
real:5.43	user:5.42	sys:0.00	max RSS:6716
******************************
parser:./bin/bench_exp_html	file:../page_habrahabr-70330.html
5.826405 s
real:5.83	user:5.78	sys:0.02	max RSS:50492
******************************
parser:./bin/bench_gokogiri	file:../page_habrahabr-70330.html
6.471175 s
real:6.56	user:5.89	sys:0.65	max RSS:1943176
******************************
parser:./bin/bench_h5	file:../page_habrahabr-70330.html
96.829145 s
real:96.83	user:96.51	sys:0.02	max RSS:62356
******************************
parser:./bin/bench_exp_html	file:../page_habrahabr-index.html
0.293615 s
real:0.29	user:0.29	sys:0.00	max RSS:4828
******************************
parser:./bin/bench_gokogiri	file:../page_habrahabr-index.html
0.361011 s
real:0.36	user:0.34	sys:0.02	max RSS:97308
******************************
parser:./bin/bench_h5	file:../page_habrahabr-index.html
4.111917 s
real:4.11	user:4.09	sys:0.00	max RSS:5472
******************************
parser:./bin/bench_exp_html	file:../page_wikipedia.html
0.370002 s
real:0.37	user:0.36	sys:0.00	max RSS:5100
******************************
parser:./bin/bench_gokogiri	file:../page_wikipedia.html
0.343510 s
real:0.35	user:0.31	sys:0.04	max RSS:106832
******************************
parser:./bin/bench_h5	file:../page_wikipedia.html
4.674953 s
real:4.67	user:4.64	sys:0.01	max RSS:6564
===============
perl
===============
******************************
parser:mojo_parser.pm	file:../page_google.html
3.89669394493103 s
real:4.02	user:3.98	sys:0.01	max RSS:8096
******************************
parser:mojo_parser.pm	file:../page_habrahabr-70330.html
75.7632319927216 s
real:75.81	user:75.56	sys:0.01	max RSS:36384
******************************
parser:mojo_parser.pm	file:../page_habrahabr-index.html
3.66110587120056 s
real:3.70	user:3.68	sys:0.00	max RSS:8288
******************************
parser:mojo_parser.pm	file:../page_wikipedia.html
4.00335907936096 s
real:4.04	user:4.01	sys:0.01	max RSS:8404
===============
ruby
===============
******************************
parser:nokogiri_parser.rb	file:../page_google.html
0.343421056 s
real:0.39	user:0.37	sys:0.01	max RSS:13124
******************************
parser:nokogiri_parser.rb	file:../page_habrahabr-70330.html
8.438090464 s
real:8.49	user:8.45	sys:0.00	max RSS:36876
******************************
parser:nokogiri_parser.rb	file:../page_habrahabr-index.html
0.363908843 s
real:0.40	user:0.40	sys:0.00	max RSS:14352
******************************
parser:nokogiri_parser.rb	file:../page_wikipedia.html
0.378928282 s
real:0.41	user:0.41	sys:0.00	max RSS:14776

$ PLATFORMS="c-libxml2" ./run.sh 100  # добавил просто для сравнения
===============
c-libxml2
===============
******************************
parser:libxml2_html_parser.c	file:../page_google.html
0.306867 s
real:0.31	user:0.28	sys:0.00	max RSS:2244
******************************
parser:libxml2_html_parser.c	file:../page_habrahabr-70330.html
6.887496 s
real:6.89	user:6.80	sys:0.06	max RSS:24292
******************************
parser:libxml2_html_parser.c	file:../page_habrahabr-index.html
0.291910 s
real:0.29	user:0.28	sys:0.00	max RSS:2412
******************************
parser:libxml2_html_parser.c	file:../page_wikipedia.html
0.374730 s
real:0.37	user:0.37	sys:0.00	max RSS:2504


На подходе Java и Haskell
> Проверять качество работы парсера в плане полноты восстановления поломанных документов не будем.
А зря, как мне кажется. Html5 включает обработку любых ошибок в документе, поэтому строго говоря, любой текст — валидный html5-документ и должен одинаково разбираться всеми парсерами.
Решил проверить Dart. Замена же JS как ни как. Может я его не умею готовить, но результаты какие-то совсем печальные.

Скрытый текст
$ PLATFORMS="dart" ./run.sh 100
===============
dart
===============
******************************
parser:html5lib_parser.dart	file:../page_google.html
38864
real:39.13	user:38.76	sys:0.27	max RSS:73740
******************************
parser:html5lib_parser.dart	file:../page_habrahabr-70330.html
686300
real:687.00	user:679.05	sys:5.72	max RSS:269588
******************************
parser:html5lib_parser.dart	file:../page_habrahabr-index.html
34617
real:34.88	user:34.48	sys:0.26	max RSS:80804
******************************
parser:html5lib_parser.dart	file:../page_wikipedia.html
36756
real:37.01	user:36.53	sys:0.33	max RSS:76560

И так, текущий список платформ:

# были изначально
pypy
python
c-libxml2
erlang
nodejs
# добавились
golang (3 шт)
haskell
java
perl
php + tidy
ruby
dart

Спасибо тем, кто присылал свои варианты парсеров!
В ближайшее время потестирую добавленные парсеры и обновлю статью (или даже вторую часть напишу).
Сделал небольшой апдейт — прикрепил результаты для новых парсеров и платформ. К сожалению, пока только как архив с CSV и графиками.
Я смотрю php + tidy весьма позитивно выглядит в тестах, ожидал более плохого результата.
Чем хорош PHP это тем, что если используемые библиотеки хорошо написаны, в данном случае на C/C++, то и скорость будет хорошей, оверхед на вызов самих функций минимальный. Совсем другое дело, когда какая-то тяжелая либа написана на самом интерпретаторе, то тогда да — туши свет, покупай апгрейд CPU…
Надо бы другие варианты еще потестить. Мне кажется, он может еще быстрее :)
Думаю можно утилизировать возможности libxml как то так php.net/manual/en/domdocument.loadhtml.php в комбинации с php.net/manual/en/class.domdocument.php#domdocument.props.recover и php.net/manual/en/class.domdocument.php#domdocument.props.stricterrorchecking

Что то вроде
$doc = new DomDocument();
$doc->recover = TRUE;
$doc->stricterrorchecking = FALSE;
$doc->loadHTML($html_string);

(сам не проверял)
Кстати, когда пробовал запустить php+tidy парсер на top1000 Alexa (тест на зависимость от размера HTML файла), получил 100500 сообщений об ошибках / Warnings, пришлось php для этого теста убрать. Если посоветуете как это поправить — будет здорово.

parser:tidy_simplexml.php       file:../pages/page_10086.cn.html
PHP Warning:  simplexml_load_string(): Entity: line 2: parser error : PEReference in prolog in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43
PHP Warning:  simplexml_load_string():  "%20http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43
PHP Warning:  simplexml_load_string():   ^ in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43
******************************
parser:tidy_simplexml.php       file:../pages/page_163.com.html
PHP Warning:  simplexml_load_string(): Entity: line 1192: parser error : Char 0xDF71 out of allowed range in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43
PHP Warning:  simplexml_load_string(): ���<U+DF71>2012���<U+05FD>����ѡ</a> in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43
PHP Warning:  simplexml_load_string():          ^ in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43
PHP Warning:  simplexml_load_string(): Entity: line 1192: parser error : PCDATA invalid Char value 57201 in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43
******************************
parser:tidy_simplexml.php       file:../pages/page_addthis.com.html
PHP Warning:  simplexml_load_string(): namespace error : Namespace prefix addthis for userid on a is not defined in /home/seriy/workspace/html_parser_bench/php
-tidy/tidy_simplexml.php on line 43
PHP Warning:  simplexml_load_string(): addthis:userid="AddThis"></a>  in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43
PHP Warning:  simplexml_load_string():                         ^ in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43
PHP Warning:  simplexml_load_string(): namespace error : Namespace prefix addthis for userid on a is not defined in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43
PHP Warning:  simplexml_load_string(): addthis:userid="addthis"></a>  in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43
PHP Warning:  simplexml_load_string():                         ^ in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43
PHP Warning:  simplexml_load_string(): namespace error : Namespace prefix addthis for usertype on a is not defined in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43
PHP Warning:  simplexml_load_string(): addthis:usertype="company" addthis:userid="167173"></a>  in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43
PHP Warning:  simplexml_load_string():                                                   ^ in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43

и т.п. Причем остальные парсеры с этим нормально справляются как то.
Скачал архив с «Save link as» в FireFox — выдает ошибку прираспаковке, но tar вроде нормальный

$ bunzip2 html_parser_bench.tar.bz2

bunzip2: html_parser_bench.tar.bz2: trailing garbage after EOF ignored
Попробуйте ещё раз скачать (обновил конфиг Nginx). Возможно были проблемы с конвертацией переносов строк или ещё чем-то (хотя у меня через Save as в FF нормально распаковывал в Linux)
Да не, я говорю — несмотря на ошибку, тар был нормальный и все распаковалось.
Сейчас перекачал — то же самое.

$ md5sum html_parser_bench.tar.bz2
cbe8903fc6803587900d474d35053793 html_parser_bench.tar.bz2

У меня Debian testing
На сервере прям проверил
/var/www/dl.seriyps.ru/docs$ md5sum html_parser_bench.tar.bz2 
cbe8903fc6803587900d474d35053793  html_parser_bench.tar.bz2


Совпадает
Значит моя локальная проблема
Отлично! Возможно для статьи пригодится ссылка на goquery: github.com/opesun/goquery
Jquery style selector engine for HTML documents, in Go.
Использует exp/html для парсинга HTML, но не знаю, модифицированный или нет.
Извиняюсь за некропост.
Если не строить дерево, то SAX (event based) парсер libhubbub показывает примерно вдвое большую скорость парсинга чем libxml2.
Sign up to leave a comment.

Articles