Серия интерфейсных оптимизаций — обфускация JS представляет провал производительности

внешний интерфейс JavaScript Chrome V8

Резюме:
Предисловие В настоящее время производительность мобильных телефонов пользователей, производительность браузера и производительность сети становятся все лучше и лучше, внутренняя логика постепенно смещается во внешний интерфейс, а внешний рендеринг становится все более распространенным явлением. Внешний рендеринг в основном полагается на JS для завершения основной логики, и JS становится все более и более важным. Файлы JS передаются в виде исходного кода, который можно легко изменить и отладить в Chrome Devtools.

предисловие

Теперь производительность мобильного телефона пользователя, производительность браузера и производительность сети становятся все лучше и лучше, внутренняя логика постепенно смещается во внешний интерфейс, а внешний рендеринг становится все более распространенным явлением. Внешний рендеринг в основном полагается на JS для завершения основной логики, и JS становится все более и более важным. Файлы JS передаются в виде исходного кода, который можно легко изменить и отладить в Chrome Devtools. Как правило, мы не хотим, чтобы основная бизнес-логика была легко понятна другим, и мы часто защищаем ее запутыванием кода.

Итак, влияет ли на производительность обфускация кода JS? Здесь мы обсуждаем реальный случай, чтобы увидеть, как 100 раз запутаться в низкой производительности JS, и подробно о том, как отслеживать и решать подобные проблемы.

Обфускация приводит к проблемам с производительностью

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

Для некоторых более важных файлов JS метод модификации абстрактного синтаксического дерева обычно используется для защиты от обфускации. Соответствующие принципы см. в статье о Zhihu:Как зашифровать JavaScript во внешнем интерфейсе

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

Однако есть и исключения: мы обнаружили, что выполнение isdsp_securitydata_send.js в бизнесе занимает очень много времени, достигая поразительных 1,6 секунды. Информация о трассировке выглядит следующим образом:

b9f9c20687e8c3221852f5b3e16b6a0ec5c91779

И когда он не использует источник путаницы для выполнения, мы обнаружили, что за 15 миллисекунд реализация завершена. Это очень очевидный случай введения путаницы в проблемы с производительностью.

Анализировать проблемы с производительностью

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

(1) Подтвердите проблему с производительностью

Вообще говоря, Chrome Trace удобнее использовать для проверки того, есть ли проблемы с производительностью при выполнении JS. Давайте сначала поговорим о том, как читать информацию о трассировке.

На картинке выше

v8.run соответствует V8ScriptRunner::runCompiledScript ядра, который представляет время выполнения JS на стороне мигания, то есть фактическое время, затраченное на выполнение JS.

V8.Execute представляет время выполнения JS внутри v8, что имеет то же значение, что и v8.run, и потребление времени аналогично.

Часть того же цвета, что и V8.ParseLazy, представляет время компиляции JS.Как видно из рисунка выше, на время компиляции приходится подавляющее большинство.

Примечание. На приведенном выше рисунке показано только значение V8 в Trace, а не трудоемкая проблема JS, которую мы хотим обсудить.

Давайте посмотрим на информацию о трассировке с проблемами производительности,

c58fc75f6053d2274141f158540706bea6a4f680

Как видно из рисунка выше, синих фрагментов под v8.run почти нет, то есть почти нет трудозатрат на компиляцию, а в основном трудоемкость на выполнение JS-кода.

Таким образом, мы можем судить о том, что время выполнения abc.js достигло поразительных 1,6 секунды, а логика этого JS очень проста, вероятно, у него будут серьезные проблемы с производительностью.

Примечание. На приведенном выше рисунке показано время, затрачиваемое на выполнение abc.js в реальной среде.

(2) Проанализируйте причину проблемы

Выше мы обнаружили большую проблему во времени выполнения abc.js, так как же нам найти точную причину проблемы?

Давайте сначала упростим задачу и извлечем этот JS для выполнения отдельно.Например, используйте следующий пример кода:

<html>
<body>
<script type="text/javascript" src="xxx.com/.../abc.js"></script>
</body>
</html>

Затем возьмите информацию о трассировке примера кода,

Как видно из приведенного выше Trace, выполнение в нем некоторых JS-функций занимает очень много времени, каждая занимает сотни миллисекунд.

Но этот исходящий JS не может найти строку кода Мы можем напрямую скопировать содержимое исходящего файла JS в приведенный выше тег

Как видно из приведенного выше рисунка, трудоемкий код занимает 2117 строк, вы можете напрямую щелкнуть, чтобы найти конкретную строку кода,

4100d1a1c02528d2a717c1d2333690cff736676a

Как видно из приведенного выше рисунка, выполнение следующей функции занимает очень много времени, занимая более 800 миллисекунд.

function a(r) {
var n = Mo;
var a = sn;
for (var o = S; o < r[L[No + J[Lo](U)](U) + P[Qo + Z[Lo](U)](U)]; o++) {
var t = ((r[yr[No + J[Lo](U)](U) + mv + xv](o) - _) * cr + X - a) % V + _;
n += String[Oa[Wo + D[Lo](U)](U) + ad + fr[Qo + Z[Lo](U)](U) + kt](t);
a = t
}
return n
}

Почему вышеуказанная функция так трудоемка? Именно здесь в игру вступают эксперты по движку JS! Анализируя выполнение движка JS нашими техническими экспертами, мы обнаружили, что String[Oa[Wo + D[Lo](U)](U) + ad + fr[Qo + Z[Lo](U)](U ) + kt]( t) Этот код на самом деле является результатом путаницы s += String.fromCharCode(p).

В чем проблема этой путаницы? Конкатенация строк и производительность поиска в движках V8 и JSC очень слабы.Например, String["toS" + "tring"](), преобразование числа в строку, является очень слабой стороной движков V8 и JSC.

Почему производительность конкатенации строк JS такая низкая?
В JavaScript строки неизменяемы и могут быть заменены только другой строкой.

var combined = "";
for (var i = 0; i < 1000000; i++) {
    combined = combined + "hello ";
}

В приведенном выше примере кода сочетание + «hello» не изменяет напрямую объединенную переменную, а создает новый временный объект для хранения результата вычисления, а затем использует временный объект для замены объединенной переменной. Следовательно, в приведенном выше цикле for будет сгенерировано большое количество временных переменных, и GC движка JS потребуется много работы, чтобы очистить эти временные переменные, что повлияет на производительность.
Примечание. Приведенный выше анализ взят изWhy is + so bad for concatenation?

Давайте пойдем дальше, чтобы проверить эффект кода от устранения путаницы строк,

<html>
<body>
<script type="text/javascript" src="xxx.com/.../abc.js"></script>
</body>
</html>

Давайте посмотрим на информацию о трассировке выполнения JS после изменения,

35469ec1652e67783f36d29daef00f19271ad1ef

Как видно из рисунка выше, isdsp_securitydata_send.js выполняется за несколько миллисекунд.

Затем мы проверяем оптимизированный эффект на реальной бизнес-странице,

8c33b60fb17dcc5f4e8559bb57c09845fdff2287

Время выполнения напрямую от 1,6 секунды, а оптимизация 15 миллисекунд, а диапазон оптимизации больше в 100 раз!

Устранение проблем с производительностью

Как видно из приведенного выше анализа, обфускация JS приводит к большому количеству сращивания строк, что приводит к значительному падению производительности.

Тогда решение проблемы очевидно, то есть убрать эти конкатенации строк, то есть уменьшить интенсивность путаницы и удалить запутанную часть строк.

После удаления части обфускации строки время выполнения isdsp_securitydata_send.js становится 15 миллисекунд, что идеально оптимизировано.

заключительные замечания

Внешний рендеринг в наши дни очень популярен, и большая часть логики страницы контролируется JS. Исходя из нашего многолетнего опыта оптимизации производительности страниц, 20-40% оптимизации производительности страниц связано с ядром браузера, а 60-80% связаны с front-end JS, то есть front-end JS является главным приоритетом. оптимизации производительности.

Итак, каковы лучшие практики для JS-оптимизации внешнего интерфейса? Ядро напрямую участвует в анализе front-end JS, стоимость очень высока, и это не долгосрочное решение.Что ядро ​​должно делать, так это расширять возможности front-end.

С точки зрения включения внешнего интерфейса, что может сделать ядро?

(1) Организуйте некоторые распространенные методы внешнего анализа в документы для внешнего использования.

(2) Закрепить опыт ручного анализа и обобщения в автоматизированных инструментах, таких как WDPS Lighthouse.

(3) Предоставьте несколько более эффективных инструментов анализа. Например, логика работы JS-движка более четко отображается в Trace.

(4) Больше обменов и сотрудничества с интерфейсом, построение взаимного доверия и углубленное сотрудничество для изучения сложных и общих проблем.

Справочная документация

Как зашифровать JavaScript во внешнем интерфейсе

Why is + so bad for concatenation?

Optimization killers

автор:Зак

Оригинальная ссылка