задний план
В проекте компании недавно использовалась функция диаграммы Ганта, поэтому был интегрирован подключаемый модуль диаграммы Ганта с открытым исходным кодом.
Основная функция диаграммы Ганта — управление проектом, она может графически представлять последовательность и продолжительность действий любого конкретного проекта через список действий и временную шкалу, как показано на рисунке ниже.
Студенты, которые играли с диаграммами Ганта, знают, что интерфейсная реализация диаграмм Ганта в основном зависит от рисования. Живопись — это технология, которая требует очень высокой производительности и разработки переднего плана. Частые интерактивные операции также приведут к дальнейшим строгим требованиям к производительности для разработки.
Феномен
Суть явления проста. Когда я перетаскиваю область просмотра диаграммы Ганта, я замечаю заикание и размытие. Все ученики понимают, что когда дело доходит до анимационных операций, связанных с рисованием, для достижения гладкой стадии требуется 60 кадров в секунду. 30 кадров в секунду почти не заикаются, а 20 кадров в секунду серьезно заикаются.
Поэтому я использовал инструмент «Статистика рендеринга кадров», чтобы сначала увидеть значение частоты кадров невооруженным глазом. Его основная функция заключается не только в наблюдении за значением fps текущей операции со страницей, но и в отслеживании использования памяти графическим процессором.Конечно, местонахождение этого инструмента также легко найти. Просто в опции рендеринга Chrome Devtools установите флажок, чтобы включить
Когда я использую этот инструмент для наблюдения за частотой кадров, а область просмотра постоянно и с постоянной скоростью скользит, я чувствую явные заикания и размытия. Самое высокое значение обнаружения составляет всего 31 кадр в секунду, а самое низкое — 26 кадров в секунду Уровень заикания в основном серьезный. Если вы перейдете на устройство более низкого уровня, эффект отображения определенно будет невообразимым.
анализировать
Теперь, когда мы нашли проблему, давайте проанализируем, в чем проблема. Затем откройте инструмент «Производительность» и начните запись. Во время записи перемещайте область просмотра плавно и с постоянной скоростью. После перемещения в течение нескольких секунд остановите запись и получите отчет об анализе, подобный этому:
И плагин диаграммы Ганта, и основной технический стек — это React. В react16, когда мы выполняем какие-то операции, которые часто запускают рендеринг, нам нужно перегенерировать vdom для компонентов, состояние которых изменилось, а затем решить, обновлять ли реальный DOM, Все это отнимает много времени.Согласно общей частоте обновления экрана (60 Гц) и самой высокой частоте обновления, поддерживаемой текущим браузером, средняя продолжительность задачи для каждого кадра обычно составляет всего 16,6 мс. Когда продолжительность однокадровой задачи превышает 16,6 мс, происходят зависания и пропуски кадров.
Однако, согласно анализу, большинство задач, сгенерированных при прокрутке на картинке выше, имеют длительность более 40 мс и даже являются длинными задачами (официальное определение длинных задач в Chrome — более 50 мс, то есть 20 кадров в секунду). Итак, давайте расширимся и посмотрим, какие события в одной задаче вызывают длительное время выполнения.
Затем нажмите на одну из задач, чтобы увеличить детали. Вы можете видеть, что первое место занимает selftime (время самовыполнения) — это анонимная функция. Продолжайте нажимать на стек кода справа, чтобы увидеть, какая строка кода выполняется дольше.
После нажатия он автоматически поможет нам перейти к исходному модулю в Devtools, а также отметит время выполнения кода в левой части функции. Из следующего анализа, строка 74 изtoLocaleDateString
отнимает много времени очень серьезно. Поскольку рендеринг и генерация функциональных компонентов/компонентов класса синхронны, длительные затраты времени снижают эффективность рендеринга, что, в свою очередь, замедляет общую частоту кадров.
горшок для преобразования часового пояса
Date.prototype.toLocaleDateString()
Функция заключается в преобразовании текстов времени на разных языках. Например
const event = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
console.log(event.toLocaleDateString('de-DE', options));
// expected output: Donnerstag, 20. Dezember 2012
console.log(event.toLocaleDateString('ar-EG', options));
// expected output: الخميس، ٢٠ ديسمبر، ٢٠١٢
console.log(event.toLocaleDateString(undefined, options));
// expected output: Thursday, December 20, 2012 (varies according to default locale)
Но как такой, казалось бы, безобидный метод мог действовать так долго?
Ввиду того, что напрямую посмотреть на эту часть исходного кода v8 относительно сложно, мы выбрали проверку полифилла toLocaleDateString — formatjs. Эта библиотека всегда существовала как полифил интернационализации для Date, включая интернационализацию часового пояса и интернационализацию текста времени.
Мы находим форматы вpackages/intl-datetimeformat/src/to_locale_string.ts
серединаtoLocaleString
метод. Этот метод создает объект формата времени:
продолжить подпискуDateTimeFormat
Реализация класса, вы можете видеть, что естьlocaleData
Переменные. Эта переменная является текстовым содержимым каждого языка, когда мы делаем интернационализацию. Также естьtzData
переменная, является содержимым базы данных часовых поясов:
Тогда следуйте, вы найдетеResolveLocale
метод является основным методом для обработки текущего выбранного часового пояса. В нем все интернационализированные тексты будут фильтроваться операциями, а затем сопоставляться с текстом на выбранном в данный момент языке (особенно трудоемкий метод indexOf на рисунке ниже)
решение
Для такого трудоемкого вызова единственным решением является добавление кэш-памятки к существующему результату выполнения. Решение очень простое: преобразовать время в метку времени как ключ кеша, сохранить его в кеше, а затем прочитать прямо из кеша:
После оптимизации снова анализируем производительность. Обнаружено, что не только значительно улучшается fps невооруженным глазом и значение, но и лонгтаск больше не существует, а среднее время задачи сжимается до 23 мс. В принципе плавность достигнута и проблема с заиканиями решена.
Тем не менее, мы должны продолжатьtoLocaleDateString
брат апи:Intl.DateTimeFormat
.
О формате Intl.DateTimeFormat
Intl.DateTimeFormat
это относительно новый API для форматирования времени. он иtoLocaleDateString
Самая большая разница в использовании заключается в том, что он поддерживает форматирование любого объекта даты, а дизайн API смещен в сторону конструкторов, что более благоприятно для дизайна кэша. Например использование:
console.log(new Intl.DateTimeFormat('en-US').format(date));
// expected output: "12/20/2020"
console.log(new Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'long' }).format(date));
// Expected output "Sunday, 20 December 2020 at 14:23:16 GMT+11"
Так же и в приведенном вышеtoLocaleDateString
После того, как оптимизация производительности завершена, она отстает от затрат времени на реагирование.Intl.DateTimeFormat
Тоже стоит разобраться. Продолжайте просматривать код, отнимающий много времени:
Установлено, что время, затрачиваемое этим методом, не является низким: 7,1 мс, есть возможности для улучшения.
И полифилловая реализация этого метода такая же, как и выше.toLocaleDateString
Согласованы, создаются экземплярыDateTimeFormat
предмет можно использовать. Единственная разница в том, что один экземпляр создается вручную, а другой — для вас:
Тогда мы продолжимIntl.DateTimeFormat
Увеличить кеш.
окончательный результат оптимизации
По правуtoLocaleDateString
идея оптимизации, нам нужно толькоIntl.DateTimeFormat
Экземпляр можно оптимизировать. Все еще занимаюсь кэшированием, ноkey
Заменен на единственный параметр региона + вариант конвертации:
После оптимизации мы снова собираем образец производительности.
Благодаря обнаружению частота кадров в секунду достигла минимум 45 и максимум 50 данных. Это в основном плавно (потому что Devtools также потребляет производительность при включении, а фактическая частота кадров выше, чем это). По сравнению с до оптимизации он увеличился на 61%. длинная задача исчезает и не существует
конец
Конечно, этот процесс оптимизации является лишь предварительной оптимизацией. Видно, что хотя затраты времени на выполнение одной задачи значительно сократились, возможности для улучшения еще есть. Чтобы быть как можно меньше, чтобы быть менее 16,6 мс, чтобы добиться полной плавности.
Подводя итог: попробуйте использоватьIntl.DateTimeFormat
заменитьtoLocaleDateString
и кэшируйте конструктор для повышения производительности. Обратите внимание на это в других интернационализированных сценариях (таких как числа и т. д.).
Кроме того, это решение по оптимизации производительности было отправлено в вышестоящий проект с открытым исходным кодом и объединено с хранилищем в версии 8.15:GitHub.com/mate mat UK/…