Испытание эффективности

Android 8.0 включает тесты производительности binder и hwbinder для пропускной способности и задержки. Хотя существует множество сценариев для обнаружения заметных проблем с производительностью, их выполнение может занять много времени, а результаты часто недоступны до тех пор, пока система не будет интегрирована. Используя предоставленные тесты производительности, вы сможете тестировать приложение в процессе разработки, выявлять серьезные проблемы на ранних этапах и улучшать удобство использования.

Проверки производительности делятся на четыре категории:

  • Пропускная способность Binder (доступно в system/libhwbinder/vts/performance/Benchmark_binder.cpp)
  • Задержка Binder (доступно в frameworks/native/libs/binder/tests/schd-dbg.cpp)
  • Пропускная способность hwbinder (доступно в system/libhwbinder/vts/performance/Benchmark.cpp)
  • Задержка hwbinder (доступно в system/libhwbinder/vts/performance/Latency.cpp).

О binder и hwbinder

Binder и hwbinder – это инфраструктуры межпроцессного взаимодействия (IPC) Android, которые используют один и тот же драйвер Linux, но имеют следующие качественные различия:

Формат Binder hwbinder
Назначение Предоставить схему межпроцессного взаимодействия общего назначения для фреймворка Обмен данными с оборудованием
Свойство Оптимизировано для использования фреймворка Android. Минимальная задержка
Как изменить правила планирования для переднего плана и фона Да Нет
Передача аргументов Использует сериализацию, поддерживаемую объектом Parcel. Использует буферы разброса и позволяет избежать избыточности при копировании данных, необходимых для сериализации Parcel.
Наследование приоритета Нет Да

Процессы Binder и hwbinder

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

Рисунок 1. Визуализация процессов binder в Systrace.

В примере выше:

  • Четыре процесса schd-dbg являются клиентскими.
  • Четыре процесса связывания – это процессы сервера (их названия начинаются с Binder и заканчиваются порядковым номером).
  • Клиентский процесс всегда связан с серверным процессом, который предназначен для этого клиента.
  • Все пары процессов клиент-сервер планируются ядром независимо и параллельно.

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

Пропускная способность и задержка

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

Рисунок 2. Пузырь задержки из-за разницы в пропускной способности и задержке.

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

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

Как обрабатывать инверсии приоритетов

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

Рисунок 3. Инверсия приоритетов в приложениях реального времени.

При использовании планировщика Linux Completely Fair Scheduler (CFS) у потока всегда есть шанс запуститься, даже если у других потоков более высокий приоритет. В результате приложения с планированием CFS обрабатывают инверсию приоритетов как ожидаемое поведение, а не как проблему. Однако в случаях, когда фреймворку Android требуется планирование в реальном времени, чтобы гарантировать привилегии высокоприоритетных потоков, инверсию приоритетов необходимо устранить.

Пример инверсии приоритетов во время транзакции Binder (поток RT логически блокируется другими потоками CFS при ожидании обслуживания потоком Binder):

Рисунок 4. Инверсия приоритетов, блокировка потоков реального времени.

Чтобы избежать блокировок, можно использовать наследование приоритета, чтобы временно повысить приоритет потока Binder до потока реального времени, когда он обрабатывает запрос от клиента реального времени. Помните, что планирование в реальном времени имеет ограниченные ресурсы и должно использоваться с осторожностью. В системе с n процессорами максимальное количество текущих потоков RT также равно n. Дополнительным потокам RT может потребоваться подождать (и, следовательно, пропустить сроки), если все процессоры заняты другими потоками RT.

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

Как запустить проверку пропускной способности

Тест пропускной способности выполняется для пропускной способности транзакций binder/hwbinder. В системе, которая не перегружена, пузырьки задержки встречаются редко, и их влияние можно устранить, если количество итераций достаточно велико.

  • Тест пропускной способности binder находится в system/libhwbinder/vts/performance/Benchmark_binder.cpp.
  • Тест пропускной способности hwbinder находится в состоянии system/libhwbinder/vts/performance/Benchmark.cpp.

Результаты тестирования

Пример результатов тестирования пропускной способности для транзакций с полезной нагрузкой разного размера:

Benchmark                      Time          CPU           Iterations
---------------------------------------------------------------------
BM_sendVec_binderize/4         70302 ns      32820 ns      21054
BM_sendVec_binderize/8         69974 ns      32700 ns      21296
BM_sendVec_binderize/16        70079 ns      32750 ns      21365
BM_sendVec_binderize/32        69907 ns      32686 ns      21310
BM_sendVec_binderize/64        70338 ns      32810 ns      21398
BM_sendVec_binderize/128       70012 ns      32768 ns      21377
BM_sendVec_binderize/256       69836 ns      32740 ns      21329
BM_sendVec_binderize/512       69986 ns      32830 ns      21296
BM_sendVec_binderize/1024      69714 ns      32757 ns      21319
BM_sendVec_binderize/2k        75002 ns      34520 ns      20305
BM_sendVec_binderize/4k        81955 ns      39116 ns      17895
BM_sendVec_binderize/8k        95316 ns      45710 ns      15350
BM_sendVec_binderize/16k      112751 ns      54417 ns      12679
BM_sendVec_binderize/32k      146642 ns      71339 ns       9901
BM_sendVec_binderize/64k      214796 ns     104665 ns       6495
  • Время – задержка при передаче данных туда и обратно, измеряемая в реальном времени.
  • ЦП – общее время, в течение которого ЦП были запланированы для теста.
  • Итерации – количество раз, когда выполнялась тестовая функция.

Например, для полезной нагрузки размером 8 байт:

BM_sendVec_binderize/8         69974 ns      32700 ns      21296

… максимальная пропускная способность связующего ПО рассчитывается следующим образом:

Максимальная пропускная способность с полезной нагрузкой 8 байт = (8 * 21296)/69974 ~= 2,423 б/нс ~= 2,268 Гб/с

Варианты тестирования

Чтобы получить результаты в формате JSON, запустите тест с аргументом --benchmark_format=json:

libhwbinder_benchmark --benchmark_format=json
{
  "context": {
    "date": "2017-05-17 08:32:47",
    "num_cpus": 4,
    "mhz_per_cpu": 19,
    "cpu_scaling_enabled": true,
    "library_build_type": "release"
  },
  "benchmarks": [
    {
      "name": "BM_sendVec_binderize/4",
      "iterations": 32342,
      "real_time": 47809,
      "cpu_time": 21906,
      "time_unit": "ns"
    },
   ….
}

Как запустить тесты задержки

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

  • Тест задержки связывания находится в папке frameworks/native/libs/binder/tests/schd-dbg.cpp.
  • Тест задержки hwbinder находится в system/libhwbinder/vts/performance/Latency.cpp.

Результаты тестирования

Результаты (в формате JSON) содержат статистику по средней, лучшей и худшей задержке, а также по количеству пропущенных сроков.

Варианты тестирования

Для тестирования задержки можно использовать следующие параметры:

Команда Описание
-i value Укажите количество итераций.
-pair value Укажите количество пар процессов.
-deadline_us 2500 Укажите срок в микросекундах.
-v Получить подробные выходные данные (отладка).
-trace Остановить трассировку по истечении срока.

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

Как указать количество итераций

Пример с большим количеством итераций и отключенным подробным выводом:

libhwbinder_latency -i 5000 -pair 3
{
"cfg":{"pair":3,"iterations":5000,"deadline_us":2500},
"P0":{"SYNC":"GOOD","S":9352,"I":10000,"R":0.9352,
  "other_ms":{ "avg":0.2 , "wst":2.8 , "bst":0.053, "miss":2, "meetR":0.9996},
  "fifo_ms": { "avg":0.16, "wst":1.5 , "bst":0.067, "miss":0, "meetR":1}
},
"P1":{"SYNC":"GOOD","S":9334,"I":10000,"R":0.9334,
  "other_ms":{ "avg":0.19, "wst":2.9 , "bst":0.055, "miss":2, "meetR":0.9996},
  "fifo_ms": { "avg":0.16, "wst":3.1 , "bst":0.066, "miss":1, "meetR":0.9998}
},
"P2":{"SYNC":"GOOD","S":9369,"I":10000,"R":0.9369,
  "other_ms":{ "avg":0.19, "wst":4.8 , "bst":0.055, "miss":6, "meetR":0.9988},
  "fifo_ms": { "avg":0.15, "wst":1.8 , "bst":0.067, "miss":0, "meetR":1}
},
"inheritance": "PASS"
}

Результаты тестирования показывают следующее:

"pair":3
Создает пару клиент-сервер.
"iterations": 5000
Включает 5000 итераций.
"deadline_us":2500
Срок – 2500 мкс (2,5 мс). Большинство транзакций должны соответствовать этому значению.
"I": 10000
Одна итерация тестирования включает две транзакции:
  • Одна транзакция с обычным приоритетом (CFS other)
  • Одна транзакция с приоритетом в реальном времени (RT-fifo)
5000 итераций – это 10 000 транзакций.
"S": 9352
9352 транзакции синхронизируются на одном ЦП.
"R": 0.9352
Указывает соотношение, в котором клиент и сервер синхронизируются на одном ЦП.
"other_ms":{ "avg":0.2 , "wst":2.8 , "bst":0.053, "miss":2, "meetR":0.9996}
Среднее (avg), худшее (wst) и лучшее (bst) время для всех транзакций, выполненных вызывающим абонентом с обычным приоритетом. Две транзакции miss срок, поэтому коэффициент выполнения (meetR) равен 0,9996.
"fifo_ms": { "avg":0.16, "wst":1.5 , "bst":0.067, "miss":0, "meetR":1}
Аналогично other_ms, но для транзакций, инициированных клиентом с приоритетом rt_fifo. Скорее всего (но не обязательно), fifo_ms покажет лучшие результаты, чем other_ms, с более низкими значениями avg и wst и более высоким значением meetR (разница может быть ещё более значительной при загрузке в фоновом режиме).

Примечание. Фоновая нагрузка может повлиять на результат тестирования пропускной способности и кортеж other_ms в тесте задержки. Только fifo_ms может показывать похожие результаты, если фоновая загрузка имеет более низкий приоритет, чем RT-fifo.

Как указать значения пар

Каждый клиентский процесс связан с серверным процессом, выделенным для клиента, и каждая пара может быть запланирована независимо для любого ЦП. Однако перенос ЦП не должен происходить во время транзакции, если флаг SYNC имеет значение honor.

Убедитесь, что система не перегружена. Высокая задержка в перегруженной системе ожидаема, но результаты тестирования в таких условиях не дают полезной информации. Чтобы проверить систему при более высоком давлении, используйте -pair #cpu-1 (или -pair #cpu с осторожностью). Тестирование с использованием -pair n с n > #cpu перегружает систему и создает бесполезную информацию.

Как указать значения сроков

После обширного тестирования пользовательских сценариев (запуска теста задержки на подходящем устройстве) мы определили, что задержка должна составлять не более 2,5 мс. Для новых приложений с более высокими требованиями (например, 1000 фотографий в секунду) этот срок будет изменен.

Укажите подробный вывод

Параметр -v позволяет получить подробные выходные данные. Пример:

libhwbinder_latency -i 1 -v

-------------------------------------------------- service pid: 8674 tid: 8674 cpu: 1 SCHED_OTHER 0
-------------------------------------------------- main pid: 8673 tid: 8673 cpu: 1 -------------------------------------------------- client pid: 8677 tid: 8677 cpu: 0 SCHED_OTHER 0
-------------------------------------------------- fifo-caller pid: 8677 tid: 8678 cpu: 0 SCHED_FIFO 99 -------------------------------------------------- hwbinder pid: 8674 tid: 8676 cpu: 0 ??? 99
-------------------------------------------------- other-caller pid: 8677 tid: 8677 cpu: 0 SCHED_OTHER 0 -------------------------------------------------- hwbinder pid: 8674 tid: 8676 cpu: 0 SCHED_OTHER 0
  • Поток сервиса создается с приоритетом SCHED_OTHER и выполняется в CPU:1 с pid 8674.
  • Первая транзакция запускается fifo-caller. Чтобы обслужить эту транзакцию, hwbinder повышает приоритет сервера (pid: 8674 tid: 8676) до 99, а также помечает его временным классом планирования (обозначается как ???). Затем планировщик переводит процесс сервера в состояние CPU:0 и синхронизирует его с тем же ЦП, что и клиент.
  • У вызывающего абонента второй транзакции приоритет SCHED_OTHER. Сервер понижает свой приоритет и обслуживает вызывающую сторону с приоритетом SCHED_OTHER.

Как использовать трассировку для отладки

Чтобы устранить проблемы с задержкой, можно указать параметр -trace. При использовании теста задержки запись трассировки останавливается в момент обнаружения недопустимой задержки. Пример:

atrace --async_start -b 8000 -c sched idle workq binder_driver sync freq
libhwbinder_latency -deadline_us 50000 -trace -i 50000 -pair 3
deadline triggered: halt ∓ stop trace
log:/sys/kernel/debug/tracing/trace

На задержку могут влиять следующие компоненты:

  • Режим сборки Android. Режим Eng обычно работает медленнее, чем режим userdebug.
  • Framework. Как сервис фреймворка использует ioctl для настройки связывания?
  • Драйвер Binder. Поддерживает ли драйвер точную блокировку? Содержит ли он все исправления, связанные с производительностью?
  • Версия ядра. Чем лучше ядро поддерживает режим реального времени, тем лучше результаты.
  • Конфигурация ядра. Содержит ли конфигурация ядра параметры DEBUG, например DEBUG_PREEMPT и DEBUG_SPIN_LOCK?
  • Планировщик ядра. Есть ли в ядре планировщик Energy-Aware (EAS) или Heterogeneous Multi-Processing (HMP)? Влияют ли на планировщик какие-либо драйверы ядра (драйвер cpu-freq, драйвер cpu-idle, cpu-hotplug и т. д.)?