Преамбула
Занимаясь сейчас написанием интерфейса/middleware для одной IPTV-STB приставки, столкнулся с достаточно медленной работой javascript-парсера встроенной Оперы при вызове eval (пока встроенной была не Опера, а ANT Galio все было еще печальнее, но это тема отдельного разговора). То есть, там вообще все работает медленно, т.к. это все-таки просто приставка, но в данном конкретном случае с тормозами надо было что-то делать — через ajax грузилась программа ТВ-передач, и пока eval парсил приходящую JSON-строку, приставка на действия пользователя вообще не откликалась (причем достаточно долго). В итоге я решил вопрос использованием JSONP, который заработал на приставке в несколько раз быстрее (а заодно и решил вопрос с кросс-доменностью), но попутно задумался над производительностью парсеров в принципе. На хабре нашел только одну статью про это, но JSONP там не было… Плюс в комментах были несколько позитивных критических замечаний, поэтому решил провести свое тестирование. Используются все парсеры с JSON.org и браузеры Opera 9.64, Firefox 3.5, Internet Explorer 8, Google Chrome 2, Safari 4.
Методика тестирования
В комментариях к вышеуказанной статье обращали внимание, что браузеры могут кэшировать результат парсинга, поэтому не вполне корректно одну и ту же строку просто «прогонять» сотню раз, чтобы получить достоверный результат. Кроме того, в JSONP парсинга как такового вообще нет — речь скорее идет о парсинге JavaScript как таковом.
В итоге был написан короткий php-скрипт, который стоит на сервере и генерирует по запросу JSON-строку, содержающую массив с заданным количеством объектов вот такого формата:
{
"key_string1" : "qkrlgthcnadzemsiuwvbfopxyj",
"key_string2" : "zbntiyjchfpsraudqxkgomvwle",
"key_int" : 25721,
"key_arr" : [21,25,19,16,10,4,27,17,6,12,22,29,5,1,26,28,23,14,24,13,3,18,30,15,2,11,8,20,9,7],
"key_float" : 2110.1
}
Содержимое свойств объектов заполняется каждый раз случайным образом, а сам скрипт вызывается с предотвращением кэширования.
Для самого тестирования была написана страница, запрашивающая разными способами с сервера JSON-строку с разным количеством объектов в массиве и замеряющая время скачивания и парсинга (в случае JSONP — все сразу). Для более точного результата все запросы проводились по 50 раз. Использованы следующие методы парсинга:
- JSONP — в самом классическом варианте — подключаемый скрипт вызывает callback с JSON-объектом в качестве параметра. Основной недостаток — низкий уровень безопасности, т.к. в подключаемом скрипте может быть что угодно.
- XMLHttpRequest + json2.js — это по сути обычный eval, только чуть-чуть облагороженный несколькими regexp'ами для проверки валидности самого кода JSON-строки. Недостаток — т.к. используется eval, все равно можно попробовать включить в строку js-код и «набедокурить» на клиенте.
- XMLHttpRequest + json_parse.js — полноценный синтаксический разбор JSON. Eval не используется, поэтому с безопасностью все в порядке, а основной недостаток — низкая скорость работы.
- XMLHttpRequest + json_sans_eval — парсер от Google. Не использует eval — поэтому безопасен, и использует минимум кода — поэтому быстр. :-) Недостаток — не проводит валидацию JSON-строки, поэтому если она «битая» или неправильная — результат непредсказуем. :-) В смысле — в результате работы парсинга будет возвращена какая-нибудь лабуда.
- XMLHttpRequest + нативные парсеры JSON — я не мог не включить в это тестирование нативные парсеры JSON, которые присутствуют в последних версиях IE и FF. :-)
Тестировал все на домашнем компьютере (Athlon64 X2 5200+, 2Гб памяти, Windows XP SP2), а скрипты запускались с сервера, находящегося неподалеку в локальной сети (чтобы хоть немного повлиять положительно на задержки, связанные с транспортом). Заранее хочу предупредить, что эти самые транспортные задержки тем не менее присутствуют в полной мере (в районе 200мс), причем с немаленьким таким разбросом, поэтому акцентироваться на абсолютных значениях тестов не стоит — намного важнее относительное «на глазок» сравнение результатов разных парсеров и браузеров. Я пробовал тестировать на localhost'е, но запущенный под shttps php-cgi работает еще медленее, а создавать специальный стенд для тестирования было немного лениво.
И что касается браузеров. Использовались официальные последние версии официальных релизов, доступные на сайтах браузеров на 02.07.2009. Поэтому, например, Опера не 10-я — чего, может быть, многие бы хотели. IE6 тоже нет — я мог запустить его разве что в IETester, но мне кажется, что результаты оттуда были бы не вполне корректны. Тест прогонялся в единственной вкладке сразу после запуска каждого браузера. В процессе его работы разные браузеры по разному грузили процессор и потребляли разное количество памяти, но это — тема для отдельного исследования.
Результаты тестирования
Все результаты — в табличной форме. Для всех парсеров в первой строке приведено общее время выполнения в миллисекундах (запрос данных + парсинг), во второй (кроме JSONP) — время только парсинга. Сравнивать с JSONP имеет смысл только первую строчку, вторая служит только для сравнения остальных парсеров между собой.
Google Chrome 2
Парсер | Количество объектов в массиве в строке JSON | |||||
---|---|---|---|---|---|---|
25 | 50 | 100 | 200 | 400 | 800 | |
JSONP | 187 | 187 | 211 | 197 | 213 | 242 |
JSON_json2 | 189 | 189 | 193 | 200 | 220 | 253 |
JSON_json2 — parse | 2 | 4 | 6 | 12 | 21 | 42 |
JSON_json_parse | 192 | 194 | 198 | 212 | 241 | 311 |
JSON_json_parse — parse | 4 | 6 | 11 | 22 | 45 | 92 |
JSON_json_sans_eval | 190 | 190 | 191 | 231 | 240 | 259 |
JSON_json_sans_eval — parse | 1 | 3 | 6 | 11 | 23 | 48 |
Firefox 3.5
Парсер | Количество объектов в массиве в строке JSON | |||||
---|---|---|---|---|---|---|
25 | 50 | 100 | 200 | 400 | 800 | |
JSONP | 212 | 215 | 219 | 222 | 233 | 277 |
JSON_json2 | 219 | 224 | 224 | 239 | 270 | 384 |
JSON_json2 — parse | 3 | 6 | 12 | 25 | 51 | 106 |
JSON_json_parse | 223 | 255 | 276 | 353 | 529 | 820 |
JSON_json_parse — parse | 19 | 36 | 72 | 145 | 288 | 578 |
JSON_json_sans_eval | 219 | 225 | 224 | 257 | 307 | 365 |
JSON_json_sans_eval — parse | 5 | 8 | 16 | 31 | 62 | 123 |
JSON_native | 198 | 211 | 219 | 233 | 244 | 261 |
JSON_native — parse | 1 | 1 | 2 | 4 | 9 | 17 |
Opera 9.64
Парсер | Количество объектов в массиве в строке JSON | |||||
---|---|---|---|---|---|---|
25 | 50 | 100 | 200 | 400 | 800 | |
JSONP | 307 | 356 | 371 | 395 | 390 | 430 |
JSON_json2 | 323 | 332 | 344 | 391 | 463 | 693 |
JSON_json2 — parse | 8 | 16 | 31 | 67 | 138 | 343 |
JSON_json_parse | 316 | 333 | 363 | 438 | 553 | 829 |
JSON_json_parse — parse | 11 | 22 | 54 | 112 | 231 | 476 |
JSON_json_sans_eval | 311 | 323 | 352 | 380 | 436 | 588 |
JSON_json_sans_eval — parse | 5 | 12 | 23 | 53 | 114 | 237 |
Internet Explorer 8
Парсер | Количество объектов в массиве в строке JSON | |||||
---|---|---|---|---|---|---|
25 | 50 | 100 | 200 | 400 | 800 | |
JSONP | 191 | 191 | 198 | 201 | 217 | 244 |
JSON_json2 | 216 | 204 | 212 | 218 | 251 | 220 |
JSON_json2 — parse | 3 | 5 | 11 | 22 | 45 | 92 |
JSON_json_parse | 238 | 287 | 399 | 523 | 864 | 1672 |
JSON_json_parse — parse | 45 | 90 | 191 | 327 | 643 | 1443 |
JSON_json_sans_eval | 203 | 213 | 273 | 306 | 412 | 681 |
JSON_json_sans_eval — parse | 9 | 20 | 54 | 107 | 204 | 448 |
JSON_native | 195 | 196 | 209 | 201 | 219 | 250 |
JSON_native — parse | 1 | 1 | 4 | 5 | 11 | 27 |
Safari 4
Парсер | Количество объектов в массиве в строке JSON | |||||
---|---|---|---|---|---|---|
25 | 50 | 100 | 200 | 400 | 800 | |
JSONP | 204 | 228 | 208 | 221 | 238 | 280 |
JSON_json2 | 198 | 200 | 202 | 210 | 232 | 266 |
JSON_json2 — parse | 1 | 2 | 4 | 8 | 16 | 31 |
JSON_json_parse | 224 | 205 | 213 | 239 | 273 | 351 |
JSON_json_parse — parse | 4 | 7 | 14 | 28 | 56 | 111 |
JSON_json_sans_eval | 202 | 205 | 211 | 226 | 260 | 324 |
JSON_json_sans_eval — parse | 3 | 5 | 10 | 20 | 41 | 80 |
UPD: deerua нарисовал для каждого парсера симпатичные графики — радуемся:
JSONP
JSON_json2
JSON_json_parse
JSON_json_sans_eval
JSON_native
Выводы
С одной стороны результаты достаточно нерепрезентативны — большинство отличий скрадывается 200 миллисекундной задержкой (и ее разбросом между разными запусками) при вызове серверного скрипта, который возвращает данные. С другой стороны ряд выводов сделать не только можно, но и нужно:
- JSONP действительно быстр! Его скорость одинакова во всех браузерах (разве что Опера подкачала), а масштабируемость намного лучше, чем у любых других парсеров. Скорость его работы зависит больше всего от самого сервера, отдающего контент, — отсюда и беспорядочный разброс в результатах. В любом случае, если нет специальных требований к безопасности на клиенте, то JSONP — оптимальный выбор для передачи и парсинга JSON-данных.
- Если речь не идет о больших объемах данных (файл на 800 объектов из этого теста занимает около 190кб) и использовании IE, то разница во времени между остальными парсерами не особо существенна. Имеется в виду полное время выполнения, а не только парсинг JSON.
- Встроенные JSON-парсеры в FF и IE показали лучшие результаты в том, что касается скорости именно парсинга. Учитывая еще, что они обеспечивают полную безопасность, — использовать их явно имеет смысл.
- Гугловский парсер json_sans_eval не сильно медленнее json2, работающего на классическом eval, зато обеспечивает высокий уровень безопасности. Поэтому если получаемый JSON-объект проходит потом отдельную валидацию в процессе использования, то это, очевидно, будет оптимальным компромиссом.
- Из браузеров лучше всего себя показали Chrome и Safari. Учитывая, что ноги у обоих растут из одного места — WebKit однозначно рулит. :-)
- Opera и Firefox выступили крайне средненько, а IE8 — неоднозначно: отличный нативный парсер и работа eval и одновременно ужасный «ручной» разбор строки. Очевидно, дело в отсталом js-движке.
Вот-с, надеюсь это было кому-нибудь интересно, кроме меня. :-) Возможно, позже я улучшу тест, убрав из нее транспортную составляющую, но для первоначальной оценки скорости парсеров и браузеров и этого, на мой взгляд, достаточно…
ps: А вот если бы Хаб позволял задавать bgcolor в строках таблиц — результаты были бы куда красивее…