Всем привет!

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

Да, в этой статье я предлагаю выложить все парсеры на стол и измерить!

Приступим!

Прежде чем что-то сравнивать, нам нужно понять: что же мы собственно хотим сравнить?! А сравнить мы хотим хтмл парсеры, но вот что такое хтмл парсер?

Хтмл парсер это:
  1. Токенизатор (Tokenizer) — разбивка текста на токены
  2. Построение дерева (Tree Builder) — размещение токенов «в нужные позиции» в дереве
  3. Последующая работа с деревом

Человек «с мороза» может сказать: — «Не обязательно строить дерево для парсинга хтмл, ведь достаточно получить токены». И к сожалению, будет не прав.
Дело в том, что для правильной токенизации хтмл нам необходимо иметь дерево под рукой. Пункт 1 и 2 неразделимы.

Приведу два примера:

Первый:
<svg><desc><math><style><a>

Результат правильной обработки:
<html>
	<head>
	<body>
		<svg:svg>
			<desc:svg>
				<math:math>
					<style:math>
						<a:math>


Второй:
<svg><desc><style><a>

Результат правильной обработки:
<html>
	<head>
	<body>
		<svg:svg>
			<desc:svg>
				<style>
					<-text>: <a>
 


После ":" идет namespace, если не указано то хтмл.

Из двух примеров видно, что элемент STYLE ведет себя по разному в зависимости от того, где находится. В первом варианте есть элемент A, а во-втором — это уже текстовый элемент.
Здесь же можно привести пример с frameset, script, title… и их разным поведением, но думаю, общий смысл ясен.

Теперь, мы точно можем сделать вывод о том, что разбивка на токены не может быть верно осуществлена без построения хтмл дерева. А следовательно и парсинг хтмл не может быть осуществлён без, как минимум, двух стадий: токенизация и построение дерева.
Что касается терминов: «строгий», «не соответствует спецификации», «light», «html 4» и тому подобные… Я уверен, что все эти термины можно смело заменить на один: «обрабатывает не правильно». Всё это абсурд.


Как и что будем сравнивать?

А вот тут самое интересное. Носить гордое звание хтмл парсера могут далеко не все, более того даже те кто именует себя токенизаторами хтмл, по сути, не являются таковыми.
Выложив все парсеры на стол у меня сразу же возник вопрос, — кого с кем сравнить?

А сравнивать будем правильные парсеры: myhtml, html5lib, html5ever, gumbo.

Именно они соответствуют последней спецификации, и их результат будет совпадать с тем что мы может увидеть в современных браузерах.
Неправильные парсеры (не соответствуют спецификации) могут сильно отличаться по скорости/памяти, но толку от этого более чем никакого, они просто неправильно обрабатывают документ.
Никакие отговорки, вроде «парсер для html 4», приниматься во внимание не будут. Мир постоянно меняется, и за ним надо успевать.

Стоит отметить, что html5ever не совсем правильный парсер. Авторы пишут, что он не проходит все тесты html5lin-test-tree-builder, то есть тесты на правильность построения дерева. В правильные парсеры он попал за старания быть правильным.
Так же, на момент написания статьи html5lib не верно строит дерево для некоторого формата хтмл. Но это всё баги, которые я надеюсь, авторы исправят.

Будем мерить время/память для 466 хтмл файлов — TOP500 alexa. 466, а не 500 потому, что не все сайты работают и не все отдают свой контент.

Для каждой страницы будет создан fork со стадиями:
  1. Полная инициализация парсера
  2. Парсинг одной страницы
  3. Освобождение ресурсов

Тек же будет тест «из жизни» — прогнать все страницы, по возможности, одним объектом. Происходить всё это будет последовательно.

К тестам!

До тестов у нас дошли: myhtml, html5ever, gumbo.

К сожалению, html5lib вылетел из тестирования. Предварительный прогон показал, что он заметно медленнее остальных. Сравнивать его нет смысла, он написан на python и он медленный, очень медленный.

MyHTML и Gumbo написаны на C. html5ever — это Rust. В расте я не силён, пока не силён, и поэтому попросил Алексея Вознюка помочь мне. Алексей согласился (респект и уважуха) и сделал сишную обёртку для тестирования парсера.

Общий результат тестов времени выполнения:


Общий результат тестов занимаемых ресурсов:



Результаты тестов времени выполнения разбитые по 100


Результаты тестов занимаемых ресурсов разбитые по 100

Результат тестов «из жизни». Прогон всех (466) страниц в одно процессе:
myhtml:
    Общее время: 0.50890; Память на старте: 1052672; Память по окончании: 32120832

gumbo:
    Общее время: 6.12951; Память на старте: 1052672; Память по окончании: 29319168

html5ever:
    Общее время: 4.50536; Память на старте: 1052672; Память по окончании: 30715904


Итоги

Бесспорный лидер по скорости — myhtml. gumbo — лидер по памяти, что не удивительно. html5ever показал себя, мягко говоря, никак. Он не быстр и по памяти не особо проявил себя, и использовать его можно только из Rust.

Тест «из жизни» показал, что отличия в памяти не значительные, а вот по скорости, я бы не постеснялся сказать, колоссальные.

Что использовалось

Оборудование:
Intel® Core(TM) i7-3615QM CPU @ 2.30GHz
8 ГБ 1600 МГц DDR3

Программное обеспечение:
Darwin MBP-Alexander 15.3.0 Darwin Kernel Version 15.3.0: Thu Dec 10 18:40:58 PST 2015; root:xnu-3248.30.4~1/RELEASE_X86_64 x86_64
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.3.0
Thread model: posix

Ссылки

Код Бенчмарка
Картинки и CSV
myhtml, gumbo, html5ever
Обвязка для html5event от Алексея

Спасибо за внимание!

P.S. от автора myhtml

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

Комментарии (11)


  1. RPG18
    16.03.2016 14:14

    Для теста "из жизни" сколько времени заняло скачивание этих страниц?


    1. lastmac
      16.03.2016 14:41

      Привет!

      Страницы были скачены заранее. Каждая страница загружается в память и потом отдается парсерам.
      Если конкретно ответить на вопрос — не могу сказать, замеры не делались.


      1. RPG18
        16.03.2016 16:35

        Интересно посмотреть на порядок цифр. Предположим что в среднем загрузка страницы происходит за 0,5 сек. Загрузка всех страниц займет 233 сек.

        В целом получилось наглядная иллюстрация подхода: если где-то хотим ускорить, то где-то придется потратить память.


        1. lastmac
          16.03.2016 17:05

          UPDATE:

          Простите, торопился и неверно прочитал коммент. Не совсем так, по памяти я гуляю и оптимизаций не делал. Там есть куда ужиматься и ускориться. Пока просто нет на это времени.


  1. rshadow
    16.03.2016 15:30

    Было бы интересно добавить https://metacpan.org/pod/HTML::TreeBuilder раз уж разные языки тестите.


    1. lastmac
      16.03.2016 16:14

      Привет!

      Я не уверен, что он обеспечивает покрытие спецификации HTML 5, а соответственно и сравнивать его с чем либо смысла нет.


  1. alekseev_ap
    17.03.2016 22:40

    Извиняюсь за глупый вопрос, но всё же интересно. А Ваш парсер может парсить HTML странички с JavaScript так, чтобы в результате получить дерево с учётом их работы? Т.е. если в HTML есть ссылка на JavaScript файл, который генерит код вставки картинки и прочего контента, то будет ли в дереве тег этого изображение вместе с остальными атрибутами (src, width, height, alt, title, ...) или будет только тег скрипта?


    1. lastmac
      17.03.2016 22:56

      Привет!

      Вполне нормальный вопрос. Нет, это только парсер с заделом на будущее. То есть он делался с мыслью о связке с JavaScript движком. Этот функционал ожидается в будущем.


      1. alekseev_ap
        18.03.2016 09:05

        А есть ли такие уже сейчас? Желательно на C/C++


        1. lastmac
          18.03.2016 09:25

          WebKit.

          Собственно ради этого и был начат проект myhtml. Расчет (быстрый расчёт) DOM на C без зависимостей (Render Tree). У меня не хватит времени и сил сделать JS engine, он будет опционально подключаем.


        1. RPG18
          18.03.2016 10:46

          Из простого для быстрого старта можно использовать классы Qt WebKit(до 5.6) или Qt WebEngine. Из них можно получить конечный html код, который можно распарсить myhtml/gumb.