Фреймворк Flutter по умолчанию работает хорошо и быстро, но означает ли это, что вам вообще не нужно думать о производительности? Нет. Абсолютно реально писать приложения Flutter, которые будут медленными. С другой стороны, также можно использовать фреймворк максимально и делать ваши приложения не только быстрыми, но и эффективными, потребляя меньше времени процессора и батареи.



Это то, что мы хотим видеть: статистически значимый результат сравнения двух версий вашего приложения по какой-то значимой метрике. Читайте дальше, чтобы узнать, как такое получить.


Существуют некоторые общие рекомендации по оптимизации производительности во Flutter:


  • Задействуйте, как можно меньше виджетов, при обновлении состояния.
  • Обновляйте состояние только при необходимости.
  • Выносите вычислительно интенсивные задачи из ваших build методов и в идеале из основного изолята.

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


Единственный полезный ответ на эти вопросы — это тестирование и измерение. Количественно определите, какое влияние на производительность оказывает каждый выбор, и принимайте решение на основе этих данных.


Хорошей новостью является то, что Flutter предоставляет отличные инструменты профилирования производительности, такие как Dart DevTools (в настоящее время в превью релизе), который включает Flutter Inspector, или вы можете использовать Flutter Inspector непосредственно из Android Studio (с установленным плагином Flutter). У вас есть Flutter Driver для тестирования вашего приложения и Profile mode для сохранения информации о производительности.


Плохая новость заключается в том, что современные смартфоны "слишком" умные.


Проблема с регуляторами


Количественная оценка производительности приложения Flutter особенно затруднена регуляторами iOS и Android. Эти демоны системного уровня регулируют скорость центрального и графического процессоров в зависимости от нагрузки. Конечно, в основном это хорошо, так как обеспечивает плавную работу при меньшем потреблении батареи.


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


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


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


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


Это лишь один пример того, как тестирование производительности мобильных приложений может быть неинтуитивным и сложным.


Ниже я делюсь некоторыми советами, которые я собрал во время работы над Flutter приложением Developer Quest для Google I/O.


Общие советы


  • Не измеряйте производительность в режиме отладки (DEBUG mode). Измеряйте производительность только в режиме профилирования (Profile mode).
  • Измеряйте на реальном устройстве, а не в iOS Simulator или Android Emulator. Программные эмуляторы отлично подходят для разработки, но имеют характеристики производительности, отличные от реальных. Flutter не позволит вам работать в режиме профилирования на ссимулированном устройстве, потому что это не имеет никакого смысла. Данные, которые вы собираете таким образом, не применимы к реальной производительности.
  • В идеале используйте точно такое же физическое устройство. Сделайте его своим специальным устройством для тестирования производительности и никогда не используйте его ни для чего другого.
  • Изучите инструменты профилирования производительности Flutter.

CPU/GPU регуляторы


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


На Android вы можете отключить эти регуляторы. Мы называем этот процесс "блокировка масштабирования".


  • Создайте сценарий, который отключит регуляторы на вашем устройстве для тестирования производительности. Вы можете использовать пример Skia для вдохновения. Вы также можете проверить Unix CPU API.
  • Возможно, вы захотите что-то менее универсальное и более легкое, если вы не проводите такую большую работу по тестированию, как Skia. Посмотрите shell-скрипт в Developer Quest, чтобы понять, куда двигаться. Например, следующая часть скрипта устанавливает CPU для регулятора userspace (единственного регулятора, который не изменяет частоту процессора самостоятельно).
    #!/usr/bin/env bash
    GOV="userspace"
    echo "Setting CPU governor to: ${GOV}"
    adb shell "echo ${GOV} > /sys/devices/system/cpu/cpu${CPU_NO}/cpufreq/scaling_governor"
    ACTUAL_GOV=`adb shell "cat /sys/devices/system/cpu/cpu${CPU_NO}/cpufreq/scaling_governor"`
    echo "- result: ${ACTUAL_GOV}"
  • Ваша цель здесь не в том, чтобы моделировать реальную производительность (пользователи не отключают регуляторы на своих устройствах), а в том, чтобы иметь сопоставимые показатели производительности между запусками.
  • В конце вам нужно поэкспериментировать и адаптировать shell-скрипт к устройству, которое вы будете использовать. Это работает, но до тех пор, пока вы не сделаете это, ваши данные о производительности будут вас обманывать.


Ранняя версия Developer Quest, тестируемая с помощью Flutter Driver на моем рабочем столе.


Flutter Driver


Flutter Driver позволяет автоматически тестировать ваше приложение. Прочитайте раздел «Профилирование производительности» на flutter.dev, чтобы узнать, как его использовать при профилировании вашего приложения.


  • Для проверки производительности не тестируйте ваше приложение вручную. Всегда используйте Flutter Driver, чтобы получать действительно показательные данные.
  • Напишите свой Flutter Driver код, чтобы он проверял то, что вы действительно хотите измерить. Если вам нужна общая производительность приложения, попробуйте пройтись по всем частям приложения и сделать то, что сделал бы пользователь.
  • Если в вашем приложении есть элемент случайности (Random, сетевые события и т.д.), то создайте для таких ситуаций "заглушки" (mock). Прогоны тестов должны быть максимально похожими друг на друга.
  • Если вы хотите, то можете добавить пользовательские события на временную шкалу, используя методы startSync() и finishSync() класса Timeline. Это может быть полезно, если вы заинтересованы в производительности определенной функции. Поместите startSync() в её начало и finishSync() в её конец.
  • Сохраните как сводку (writeSummaryToFile), так и, что более важно, необработанную временную шкалу (writeTimelineToFile).
  • Каждую версию вашего приложения тестируйте много раз. Для Developer Quest я проводил по 100 запусков. (Когда вы измеряете вещи, которые могут быть зашумлены, например, по p99-метрике, вам может потребоваться намного больше прогонов.) Для систем на основе POSIX это просто означает выполнение чего-то вроде следующего: for i in {1..100}; do flutter drive --target=test_driver/perf.dart --profile; done.

Инструмент временной шкалы в Chrome для проверки результатов профилирования во Flutter.


Timeline


Timeline — это необработанный вывод результатов вашего профилирования. Flutter пишет эту информацию в файл JSON, который можно загрузить в chrome://tracing.


  • Разберитесь, как открыть полную временную шкалу в Chrome. Вы просто открываете chrome://tracing в браузере Chrome, нажимаете «Load» и выбираете файл JSON. Вы можете прочитать больше в этом небольшом руководстве. (Существует также инструмент временной шкалы Flutter, который в настоящее время находится в тех-превью. Я не использовал его, потому что проект Developer Quest был запущен до того, как инструменты Flutter были готовы.)
  • Используйте клавиши WSAD для перемещения по временной шкале в chrome://tracing и 1234 для изменения режимов работы.
  • При первой настройке тестирования производительности рассмотрите возможность запуска Flutter Driver с Android-инструментом Systrace. Это дает вам более полное представление о том, что на самом деле происходит в устройстве, включая информацию о масштабировании частоты процессора. Не измеряйте полностью приложение с помощью Systrace, так как это сделает всё медленнее и менее предсказуемым.
  • Как запустить Android Systrace вместе с Flutter Driver? Во-первых, запустите Android Systrace с помощью /path/to/your/android/sdk/platform-tools/systrace/systrace.py --atrace-categories=gfx,input,view,webview,wm,am,sm,audio,video,camera,hal,app,res,dalvik,rs,bionic,power,pm,ss,database,network,adb,pdx,sched,irq,freq,idle,disk,load,workq,memreclaim,regulators,binder_driver,binder_lock. Затем приложение flutter run test_driver/perf.dart --profile --trace-systrace. Наконец, запустите Flutter Driver flutter drive --driver=test_driver/perf_test.dart --use-existing-app=http://127.0.0.1:NNNNN/ (где NNNNN — это порт, который дает вам запуск flutter приложения выше).

Метрики


Лучше посмотреть как можно больше метрик, но я решил, что некоторые более полезные, чем другие.


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


  • Не рассматривайте TimelineSummary.frameCount как способ вычисления кадров в секунду (FPS). Инструменты профилирования Flutter не дают реальной информации о частоте кадров. TimelineSummary предоставляет метод countFrames(), но он подсчитывает только количество завершенных сборок кадра. Хорошо оптимизированное приложение, которое ограничивает ненужные ребилды (обновления), будет иметь меньший FPS, чем неоптимизированное приложение, которое ребилдится часто.


  • Лично я получаю самые полезные данные, измеряя общее время процессора, потраченное на выполнение кода Dart. Это подсчитывает код, выполненный как в ваших build методах, так и вне их. Предполагая, что вы запускаете тесты профилирования на устройстве с блокировкой масштаба, общее время ЦП можно считать хорошим приближением к тому, насколько больше / меньше батареи будет потреблять ваше приложение.


  • Самый простой способ узнать общее время процессора, потраченное на выполнение кода Dart, — это оценить количество MessageLoop:FlushTasks событий на временной шкале. Для Developer Quest я написал инструмент Dart для их извлечения.


  • Чтобы обнаружить "джанк" (хлам) (то есть пропущенные кадры), ищите экстремумы. Например, для конкретного случая Developer Quest и устройства, на котором мы тестировали, полезно взглянуть на время сборки 95-го процентиля. (Время сборки 90-го процентиля было слишком похожим, даже если сравнивать код с совершенно разными уровнями эффективности, а числа 99-го процентиля, как правило, зашумлены. Ваш показатели могут отличаться.)


  • Как уже упоминалось выше, тестируйте каждую версию вашего приложения несколько (возможно, 100) раз. Затем используйте средние или процентильные данные с полями ошибки. Еще лучше использовать диаграммы размаха.



Результаты


После настройки вы сможете уверенно сравнивать коммиты и проводить эксперименты. Ниже вы можете увидеть ответ на распространенную дилемму: «стоит ли эта оптимизация затрат на обслуживание?»


Я думаю, что в данном конкретном случае ответ — да. Благодаря всего лишь нескольким строкам кода каждое автоматизированное прохождение нашего приложения занимает в среднем на 12% меньше процессорного времени.


Но — и это главное сообщение этой статьи — измерения другой оптимизации могут показать что-то совсем другое. Заманчиво, но неправильно пытаться экстраполировать измерение производительности слишком широко.


Другими словами: "как повезёт". И мы должны с этим смириться.

Комментарии (0)