Однажды я столкнулся с загадочной задачей в нашем OpenProject, поставленной передо мной:

Ускорьте загрузку обзора рабочих

Для справки, рассматриваемый обзор выглядит так:

В этом обзоре перечислены доступные рабочие для определенной смены, выделенные цветом для облегчения поиска. Эта доступность также может иметь примечание. Для тех, кто разбирается в чешском языке, эти сообщения могут показаться забавными, потому что в них перечислены такие примечания, как «Путешествие», другие могут включать «Моя жена разрешила мне работать только в дневную смену» и т. д. Это может показаться грязным (и это так), но наши клиенты привыкли к этому, и это полезный инструмент, который поможет им быстро найти доступность и прикрепить примечания.

Проблема заключалась в том, как вычислялась ширина одной ячейки этой таблицы, ведь мы хотим, чтобы сообщение было читабельным на всех разрешениях. Чтобы выяснить, поместится ли его диапазон в одну «подячейку» обзора, прошлые разработчики вычисляли точную ширину его текста относительно своей машины (размер шрифта, коэффициент масштабирования, требуемую/минимальную ширину ячейки и т. д.) с помощью jQuery. . Если бы он не помещался, вместо этого при наведении отображалась бы всплывающая подсказка с этим сообщением или с примечанием, обозначающим его содержимое внизу для очень старых браузеров (например, IE8 и Safari x.godknowswhat).

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

Почему тогда медленно? Конечно, «невидимый» узел не вызвал бы никаких проблем. Да. После вызова jQuery().remove() на этом временном узле весь документ был принудительно переформатирован, что привело к зависанию клиента.

Ну это было легко. Но допустим, вы плохо разбираетесь в реализации вызовов браузера, управлении потоком элементов в его движке и причинах медленного рендеринга. Как самому найти причину зависаний?

Я нашел это, перейдя на вкладку производительности (на мой взгляд, преступно недооцененную) в инструментах разработки Chrome. Я провел профилирование и был ошеломлен представленной мне информацией.

Масштабы этих графиков, безусловно, сложны на первый взгляд. Запутанные, с цветовой кодировкой, с подгонкой или неподгонкой текста по ширине ячейки (сходство с нашим интерфейсом чистое совпадение).

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

Давайте увеличим только интересную часть.

Из всего текста ясно видно, что это зависание вызвано вызовом функции. Кто знал! Эти подлые функции, отнимающие у наших пользователей взаимодействие!

Главное дерево представляет собой стек (опять же, не совсем точный) вызываемых функций. Мы видим, что функция, которая вызывается на detail?tid=673&tab=1 в строке 13285, вызывает зависание. Ну, по крайней мере, за это отвечает дерево, которое вызывает эта функция. Затем под этой функцией где находится анонимная функция той же длины. Спойлер, это обратный вызов в $(document).ready(function() {/*code*/}) мантре. Под обратным вызовом ready() мы, наконец, получаем вызов nBodyRepos().

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

И неудивительно, что dev-tools предлагает нам восходящее представление одного узла в графе. Посмотрим…

Ну, узкое место явно вызвано jQuery, в строке 6128 jquery.js. Дело закрыто, мы выиграли, jQuery непригоден, перепишите все 100к строк кода в React и все будет хорошо. /сарказм

Если мы посмотрим на нашкод, вызывающий зависание (нажав на полезную ссылку на исходный файл справа от восходящего дерева), мы увидим:

$('.notetable-fontsample').each(function() {
    var tt = $(this);
    /* Unrelated code */
    tt.remove();
});

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

Теперь мы знаем, что критический код находится внутри этого обратного вызова к each(), и он как-то связан с удалением узла. Давайте прокомментируем это.

После удаления tt.remove() узкое место сократилось с 2,6 с (+- 13,15%) до 330 мс (+- 27,91%) (проценты обозначают отклонение за 10 запусков). Узел только обновляется, а не создается+удаляется каждый цикл предыдущего кода. Код далек от хорошего, но переписывать старые, унаследованные и загнивающие системы всегда больно.

Для справки:

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

tl;dr:Используйте вкладку производительности chrome dev-tools, если ваш старый код зависает на стороне клиента.