Это, что называется, "вштырило" :-). Новая версия highlight.js меня так впечатлила (в особенности процесс ее производства), что заслужила в моих глазах сразу "мажорного" скачка после последних изменений:

Дальше — подробности для интересующихся Javascript'ом.

Предыстория

Серия событий, приведших к переписыванию парсинга, началась, когда я наткнулся на ответ в "Книге жалоб и предложений" Королевства Delphi о том, почему highlight.js не стоит использовать там:

Очень долго парсит, особенно под Оперой, 500 строчек текста секунд 30 обрабатывает, при этом проц загружен на 100%. К тому же там явно есть ошибки, выделяются не все слова (у меня например end в объявлении класса никогда не выделяется жирным) и по моему нет поддержки асма.

Действительно, все справедливо. Баг с невыделением end я записал "на подумать", а скорость решил не трогать, потому что для моих применений — короткие фрагменты в блоге — она была, в общем-то, приемлемой. Тем не менее, где-то глубоко в мозгу по поводу скорости "осадок остался": было что-то кардинально неправильное в том, что время обработки каких-то нескольких строчек вообще так заметно. При нынешних-то скоростях процессоров...

Потом Антон Ковалёв прислал Ruby, и я решил, что будет неплохо заодно и Delphi починить для выпуска новой версии.

Финалом серии событий стала оброненная вчера в разговоре с одним питонщиком фраза по поводу highlight.js, что это ж, мол, Javascript, конечно он медленный будет... Я решил подумать :-)

Хак

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

keywords: ['else', 'for', 'if', 'while']

И он был отсортирован по алфавиту, чтобы по нему можно было искать бинарным поиском. Однако его можно очень просто заменить на поиск по хеш-таблице, который быстрее. В Javascript'е в виде хеш-таблицы можно использовать любой объект, потому что при обращении к его свойствам они ищутся как раз по хешу и делается. В итоге keywords превращаются в такого аккуратного крокодильчика:

keywords: {'else': 1, 'for': 1, 'if': 1, 'while': 1}

Теперь достаточно спросить if (keywords[some_word]), который если такое свойство есть, быстро отдаст "1", что то же самое, что "true" (само слово "true" я решил не использовать, чтобы место в исходнике не занимать, там этих keyword'ов — толпа :-)).

На моем тестовом примере, где включены все поддержанные языки, скорость обработки сократилась с 3 секунд до 2,7. 10% — в общем-то неплохо для простого изменения.

Дальше я стал размышлять над проблемой "end". Процесс расцветки, в общих чертах, заключается в последовательном проходе всей исходной строки от начала до конца, но не посимвольно, а кусочками, которые выкусываются регулярками. Они могут означать разные вещи, поэтому каждый потом отдельно проверяется, что он собой представляет:

Классы в Delphi у меня начинаются на "= class" и заканчиваются на "end;". И когда вот этот "end;" появляется на проверке, он правильно определяется как конец класса, но в нем самом keyword'ы уже не ищутся, поэтому часть "end" (без точки с запятой) не подсвечивается. Чтобы их подсветить, используя уже написанную логику, пришлось бы внутри этих ограничительных выражений тоже организовывать повторный парсинг. А это выглядит слишком тяжеловесно, да и не получалось по другим причинам (пришлось бы для них определять отдельные мини-языки, что вообще ни в какие ворота).

И вот тогда я придумал переделать парсинг по-другому:

Образно выражаясь, один процесс, наступающий себе на хвост, заменяется на два, делающих каждый свое дело.

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

Но на самом деле, скорости есть от чего расти.

И вот итог: delphi'йский код из 502 строк расцвечивается у меня в Firefox 2 вообще незаметно глазу.

Комментарии: 22

  1. Олег

    Супер!

  2. Kildor

    Opera 9.1 — дельфийский код расцвечивается моментально.

  3. Горбунов Олег

    Ура! В очередной раз восхищен! =)

  4. Slaff

    Ура! Бежим обновляться :)

  5. Иван Сагалаев

    Опера, кстати, и на прошлом коде была заметно быстрее остальных.

  6. Антон Ковалёв

    Супер!

    Пример с Delphi у меня тоже расцвечивается моментально. Safari.

  7. arty

    насколько я понимаю, высокая скорость яваскрипта в опере доменстрируется обычно именно на работе со строками, так что имхо все логично

    к сожалению, во многих веб-приложениях у неё дела намного хуже, чем у FF

  8. [...] Проба highlight.js 2.1: [...]

  9. Владимир

    Класс ! Уже обновился :)

  10. Максим

    А может было бы удобнее если дополнительные языки подсветки реализовывались, через отдельные js-скрипты, дабы облегчить код?

  11. Иван Сагалаев

    Пока мне так не кажется. Несколько файлов подключать менее удобно, чем один, а размер у него сейчас достаточно маленький (34 КБ несжатых), чтобы не думать об оптимизации.

  12. Murkt

    А чем отличается версия 2.1 от 2.0?

  13. Иван Сагалаев

    В 2.1 маленький глючок поправил: в Java не подсвечивались слова "class" и "interface".

  14. ВВ

    Никто еще не пробовал прицеплять сей замечательный скрипт к textarea в designmode?

  15. Петр

    Не пробовал, но уверен, что это сделать возможно и, должно быть, очень просто.

    Но именно textarea не дизайнмодится, переносимо модится iframe.

  16. Yantar

    Спасибо за такой подробный рассказ, очень познавательно :-)

  17. Sigura

    Мои программеры на испытательный срок получили задание - написать багтрекер на ruby. Обязательно попрошу подключить к нему highlight.js.

    Когда начал обдумывать детали эксплуатации нашего багтрекера при реализации основного проекта… планируется использовать: ruby on rails, asp.net (c#), sql server ну и конечно html, css и js то есть в общем случае запускать автоопределение языка не хочется, т.к. при описании бага или написании комментария пишущий на 100% знает какой кусок кода добавляет и тратить время на ожидание автоопреления языка не хочется.

    Можно было бы вставлять какую-нибудь подсказку для highlight.js в код

    ...
    

    Может быть есть какое-то решение?

    Спасибо!

  18. Sigura

    Упс... в коде я хотел что-то такое написать:

    ...

    надеюсь сейчас получиться.

  19. Sigura

    Облажался, не дочитал до "Эвристика" в описании Highlight.js
    PS: ошибки с моей стороны не произошло, если бы это описание парой строк было бы в разделе "Подключение и использование"

  20. Максим

    Иван Сагалаев, а Вы не смотрели на вот эту разработку http://homepages.nildram.co.uk/~9jack9/download/star-light.zip, довольно интересное решение.

  21. Иван Сагалаев

    Посмотрел. Для тех, кто тоже захочет посмотреть, вот ссылка на страницу автора: http://dean.edwards.name/my/behaviors/#star-light.htc.

    Интересно, что расцветка применяется через behaviors в IE и XBL в Gecko-браузерах. Это теоретически делает его работу быстрей, чем у моего highlight.js, хотя на глаз я этого не заметил.

    Но мне не понравилась пара вещей:

    • он не использует <pre><code></code></pre>, а использует <pre class="..."></pre>, что странно, учитывая близкое знакомство Дина Эдвардса с HTML5, который стандартизует именно первый вариант
    • там нет автоопределени языка
    • сами цвета и эффекты раскраски задаются не в CSS, что лично для меня очень неудобно

    Первые два пункта, кстати, делают его совершенно бесполезным при использовании markdown для написания текста.

  22. Borys

    Спасибо, за highlight.js

    вот тут еще один конкурент...
    http://www.dreamprojections.com/syntaxhighlighter/

Добавить комментарий