tgoop.com/smelukov_dev/70
Last Update:
Привет! Небольшая заметка о том, как я затаскивал поддержку больших статов в генерируемые Статоскопом HTML-отчеты.
Как мы с вами уже знаем, у JS-движков есть ограничение на максимальный размер строки (например 512мб для V8)
Это значит, что при попытке получить строку большего размера, мы получим ошибку RangeError: Invalid string length
Очевидно, что для статов размером больше 512mb тот же JSON.parse
работать не будет, но как раз JSON.parse
нам и нужен.
Обойти ограничение можно при помощи поточного JSON-парсера. Такому парсеру нужен источник строки, который будет отдавать ее не целиком, а кусочками (например по 64кb). По мере того, как парсер получает кусочки, он пытается их распарсить - превратить в объект. Очевидно, что этот объект растет по мере парсинга. Смысл в том, что мы не обрабатываем большие строки, а следовательно, у нас не возникает исключения RangeError
, а сам объект путь себе растет пока хватает оперативной памяти.
Ну хорошо, делаю первый подход - взял поточный парсер от @rdvornov, формирую HTML и инъекчу в него код вроде такого:
const chunks = [
сюда вставляю большой JSON порубленный на строки по 64kb
];
const data = await jsonExt.parseChunked(() => chunks);
Statoscope(data);
Запускаю - не работает, браузер просто зависает 🤔
Методом проб и ошибок выяснил, что бразуер просто не переваривает такой большой тег скрипт (несколько сот мегабайт). Никакой особо полезной информации я по этому ограничению не нашел, но стало ясно, что теперь я воткнулся в проблему лимитов самого браузера. Стал думать что здесь можно сделать. Пришла идея попилить на куски не только JSON, но и теги script:
<script>
chunks.push(КУСОК_JSON)
</script>
<script>
chunks.push(СЛЕДУЮЩИЙ_КУСОК_JSON)
</script>
..... здесь еще много подобных тегов script
<script>
const data = await jsonExt.parseChunked(() => chunks);
Statoscope(data);
</script>
Запустил, заработало! Теперь я смог обойти и ограничение на
JSON.parse
и ограничение браузера на размер тега script.Учитывая то, что загружать в отчет можно сразу несколько файлов со статами, например, для сравнения сборок, предусмотрел пуш чанка по идентификатору стата:
<script>
api.pushChunk("stat1.json", КУСОК_JSON)
</script>
<script>
api.pushChunk("stat2.json", КУСОК_JSON)
</script>
<script>
api.pushChunk("stat1.json", КУСОК_JSON)
</script>
Таким образом даже не важен порядок, в котором статы сбрасываются в отчет.
Прошло какое-то время и @rdvornov задал интересный вопрос: "Слушай, а почему бы не использовать теги script не как скрипт, а как текст, тогда можно было бы сэкономить на парсинге?"
И действительно, если сказать браузеру, что содержимое script - это не скрипт, а текст, то браузер не будет тратить время на парсинг сожержимого. А, на минуточку, 64kb x Nk тегов script - это ощутимо.
В итоге получилось что-то вроде:
<script type="text/plain" data-id="stat1.json">КУСОК_JSON</script>
<script type="text/plain" data-id="stat1.json">КУСОК_JSON</script>
......
<script>
for (const element of document.querySelectorAll('script')) {
api.pushChunk(element.dataset.id, element.textContent);
}
const data = await jsonExt.parseChunked(() => api.getChunks());
Statoscope(data);
</script>
Там конечно чуть сложнее, полную версию изменений можно посмотреть тут
Итог этой простой манипуляции такой: время загрузки отчета со статами в 650mb сократилась с 21 секунды до 14 (профит в 33%!!!)
Мораль: почти всегда можно что-то придумать, чтобы стало лучше (даже когда кажется, что нельзя) 😉
BY Сергей Мелюков
Share with your friend now:
tgoop.com/smelukov_dev/70