Teste de desempenho

O Android 8.0 inclui testes de desempenho de binder e hwbinder para capacidade de processamento latência de rede. Existem muitos cenários para detectar uma performance perceptível problemas, executar tais cenários pode ser demorado e os resultados são ficará indisponível até que o sistema seja integrado. Usando o modelo de dados facilita os testes durante o desenvolvimento e a detecção de problemas graves antes e melhorar a experiência do usuário.

Os testes de desempenho incluem as quatro categorias a seguir:

  • de capacidade do binder (disponível em system/libhwbinder/vts/performance/Benchmark_binder.cpp)
  • latência do binder (disponível em frameworks/native/libs/binder/tests/schd-dbg.cpp)
  • Capacidade de processamento do hwbinder (disponível em system/libhwbinder/vts/performance/Benchmark.cpp)
  • latência de hwbinder (disponível em system/libhwbinder/vts/performance/Latency.cpp)

Sobre binder e hwbinder

Binder e hwbinder são a comunicação entre processos (IPC) do Android infraestruturas que compartilham o mesmo driver Linux, mas têm os seguintes diferenças qualitativas:

Proporção fichário Hwbinder
Objetivo Fornecer um esquema de IPC de uso geral para o framework Comunicar-se com hardware
Propriedade Otimizado para uso do framework do Android Baixa latência de sobrecarga mínima
Mudar a política de programação em primeiro/segundo plano Sim Não
Transmissão de argumentos Usa serialização compatível com o objeto Parcel Usa buffers de dispersão e evita o overhead para copiar os dados necessários para Serialização do lote
Herança de prioridade Não Sim

Processos de vinculação e hwbinder

Um visualizador do Systrace mostra transações da seguinte maneira:

Figura 1. Visualização do Systrace do binder processos.

No exemplo acima:

  • Os quatro (4) processos schd-dbg são processos do cliente.
  • Os quatro (4) processos de vinculação são processos de servidor (o nome começa com Binder e termina com um número de sequência).
  • Um processo cliente é sempre pareado com um processo de servidor, que é dedicado para o cliente.
  • Todos os pares de processos cliente-servidor são programados de modo independente pelo kernel concomitantemente.

Na CPU 1, o kernel do SO executa o cliente para emitir a solicitação. Em seguida, usa a mesma CPU sempre que possível para ativar um processo de servidor, lidar com o solicitação, e o retorno ao contexto depois que a solicitação é concluída.

Capacidade de processamento x latência

Em uma transação perfeita, em que o processo do cliente e do servidor trocam os testes de capacidade e latência não produzem resultados muito diferentes e envio de mensagens. No entanto, quando o kernel do SO está processando uma solicitação de interrupção (IRQ, na sigla em inglês) de hardware, aguardando bloqueios ou simplesmente optando por não lidar com uma mensagem uma bolha de latência pode se formar.

Figura 2. Balão de latência devido a diferenças em capacidade de processamento e latência.

O teste de capacidade gera um grande número de transações com diferentes tamanhos de payload, proporcionando uma boa estimativa do tempo de transação regular (em melhores cenários de caso) e a capacidade de processamento máxima que o binder pode atingir.

Por outro lado, o teste de latência não realiza nenhuma ação no payload para minimizar ao tempo normal da transação. Podemos usar o tempo da transação para estimar o binder sobre as despesas gerais, fazer estatísticas para a pior das hipóteses e calcular a razão entre para transações com latência que atendam a um prazo especificado.

Processar inversões de prioridade

Uma inversão de prioridade ocorre quando uma linha de execução com maior prioridade é aguardando uma linha de execução com prioridade mais baixa. As inscrições em tempo real (RT) têm um problema de inversão de prioridade:

Figura 3. Inversão de prioridade em tempo real aplicativos.

Ao usar a programação do Linux Completely Fair Scheduler (CFS), uma linha de execução sempre tem chance de ser executada mesmo quando outras linhas de execução tiverem uma prioridade mais alta. Como resultado, aplicativos com programação de CFS lidam com a inversão de prioridade como o comportamento esperado e não como um problema. Nos casos em que o framework do Android precisa da programação em tempo real para garantir o privilégio das linhas de execução de alta prioridade. No entanto, a inversão de prioridade precisam ser resolvidas.

Exemplo de inversão de prioridade durante uma transação de vinculação (a linha de execução RT é bloqueado logicamente por outras linhas de execução de CFS ao aguardar uma linha de execução de vinculação serviço):

Figura 4. Inversão de prioridade, bloqueada em tempo real linhas de execução.

Para evitar bloqueios, você pode usar a herança de prioridade para escalar temporariamente a thread binder a uma thread RT ao atender a uma solicitação de um cliente RT. Tenha em mente que a programação RT tem recursos limitados e deve ser usada com cuidado. Em um sistema com n CPUs, o número máximo de RT atuais linhas de execução também é n. linhas de execução RT adicionais podem precisar esperar (e, portanto, perder os prazos) se todas as CPUs forem usadas por outras conversas em tempo real.

Para resolver todas as inversões possíveis de prioridade, você pode usar a prioridade herança para binder e hwbinder. No entanto, como o binder é amplamente usado em todo o sistema, ativar a herança de prioridade para transações de binder pode enviar spam ao sistema com mais tópicos RT do que ele consegue processar.

Executar testes de capacidade

O teste de capacidade de processamento é executado em relação à capacidade de processamento de transações de binder/hwbinder. Em em um sistema não sobrecarregado, os bolhas de latência são raras e o impacto delas podem ser eliminados, desde que o número de iterações seja alto o suficiente.

  • O teste de capacidade do binder está em system/libhwbinder/vts/performance/Benchmark_binder.cpp
  • O teste de capacidade do hwbinder está em system/libhwbinder/vts/performance/Benchmark.cpp

Resultados dos testes

Exemplo de resultados de teste de capacidade para transações que usam payload diferente tamanhos:

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
  • Horário: indica o atraso de ida e volta medido em tempo real.
  • CPU indica o tempo acumulado em que as CPUs são programadas para o teste.
  • Iterações indica o número de vezes que a função de teste executada.

Por exemplo, para um payload de 8 bytes:

BM_sendVec_binderize/8         69974 ns      32700 ns      21296

... a capacidade máxima que o binder pode alcançar é calculada como:

Capacidade MÁXIMA com payload de 8 bytes = (8 * 21.296)/69974 ~= 2,423 b/ns ~= 2,268 Gb/s

Opções de teste

Para receber resultados em .json, execute o teste com a Argumento --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"
    },
   ….
}

Executar testes de latência

O teste de latência mede o tempo que o cliente leva para começar inicializar a transação, alternar para o processo do servidor para processamento e para receber o resultado. O teste também procura comportamentos ruins conhecidos do programador que podem afetar negativamente a latência da transação, como um programador que não oferecer suporte à herança de prioridade ou respeitar o flag de sincronização.

  • O teste de latência do binder está frameworks/native/libs/binder/tests/schd-dbg.cpp:
  • O teste de latência do hwbinder está system/libhwbinder/vts/performance/Latency.cpp:

Resultados dos testes

Os resultados (em .json) mostram estatísticas de latência média/melhor/piora e as número de prazos perdidos.

Opções de teste

Os testes de latência usam as seguintes opções:

Comando Descrição
-i value Especifique o número de iterações.
-pair value Especifique o número de pares de processos.
-deadline_us 2500 Especifique o prazo em nós.
-v Recebe uma saída detalhada (de depuração).
-trace Interromper o rastro em um hit de prazo.

As seções a seguir detalham cada opção, descrevem o uso e fornecem resultados de exemplo.

Especificar iterações

Exemplo com um grande número de iterações e saída detalhada desativada:

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"
}

Os resultados desses testes mostram o seguinte:

"pair":3
Cria um par de cliente e servidor.
"iterations": 5000
Inclui 5.000 iterações.
"deadline_us":2500
O prazo é de 2.500 us (2,5 ms); espera-se que a maioria das transações atinja esse .
"I": 10000
Uma única iteração de teste inclui duas (2) transações:
  • Uma transação por prioridade normal (CFS other)
  • Uma transação por prioridade em tempo real (RT-fifo)
. 5.000 iterações equivalem a um total de 10.000 transações.
"S": 9352
9.352 das transações são sincronizadas na mesma CPU.
"R": 0.9352
Indica a proporção de sincronização entre cliente e servidor no a mesma CPU.
"other_ms":{ "avg":0.2 , "wst":2.8 , "bst":0.053, "miss":2, "meetR":0.9996}
A média (avg), a pior (wst) e a melhor (bst) caso para todas as transações emitidas por um autor da chamada de prioridade normal. Duas transações miss o prazo, tornando a proporção de atendimento (meetR) 0,9996.
"fifo_ms": { "avg":0.16, "wst":1.5 , "bst":0.067, "miss":0, "meetR":1}
Semelhante a other_ms, mas para transações emitidas pelo cliente com Prioridade rt_fifo. É provável (mas não obrigatório) que os fifo_ms tem um resultado melhor do que other_ms, com menor Valores avg e wst e um meetR maior A diferença pode ser ainda mais significativa com a carga em segundo plano.

Observação:a carga em segundo plano pode afetar a capacidade de processamento e a tupla other_ms no teste de latência. Somente o O fifo_ms pode mostrar resultados semelhantes, desde que o carregamento em segundo plano tenha uma prioridade menor que RT-fifo.

Especificar os valores do par

Cada processo do cliente é pareado com um processo de servidor dedicado ao cliente, e cada par pode ser programado de maneira independente para qualquer CPU. No entanto, a CPU a migração não deve acontecer durante uma transação, desde que a flag SYNC seja honor:

Confira se o sistema não está sobrecarregado. Embora a alta latência em um ambiente é esperado, os resultados do teste de um sistema sobrecarregado não fornecem informações úteis informações imprecisas ou inadequadas. Para testar um sistema com pressão mais alta, use -pair #cpu-1 (ou -pair #cpu com cuidado). Testes usando -pair n com n > #cpu sobrecarrega o e gera informações inúteis.

Especificar valores do prazo

Após testes extensivos do cenário do usuário (executar o teste de latência em um produto qualificado), determinamos que o prazo a ser cumprido é de 2,5 ms. Para novos aplicativos com requisitos mais altos (como mil fotos/segundo), esse o valor do prazo será alterado.

Especificar saída detalhada

O uso da opção -v exibe a saída detalhada. Exemplo:

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
  • A linha de execução de serviço é criada com uma SCHED_OTHER e executada em CPU:1 com pid 8674.
  • A primeira transação é iniciada por um fifo-caller Para atender a essa transação, o hwbinder atualiza o prioridade do servidor (pid: 8674 tid: 8676) como 99 e também a marca com uma classe de programação temporária (impressa como ???). O programador coloca o processo do servidor em CPU:0 para ser executado e o sincroniza com o mesma CPU com o cliente.
  • O autor da segunda transação tem um Prioridade SCHED_OTHER. O servidor faz downgrade a si mesmo e atende aos autor da chamada com prioridade SCHED_OTHER.

Usar rastros para depuração

É possível especificar a opção -trace para depurar problemas de latência. Quando usado, o teste de latência interrompe a gravação do tracelog no momento em que um latência é detectada. Exemplo:

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

Os seguintes componentes podem afetar a latência:

  • Modo de build do Android. O modo Eng geralmente é mais lento que userdebug.
  • Framework integrado. Como o serviço de framework usa ioctl para configurar o binder?
  • Driver de binder. O driver oferece suporte a configurações travando? Ele contém todos os patches de melhoria de desempenho?
  • Versão do kernel. Quanto melhor for a capacidade do kernel em tempo real, tiver, melhores serão os resultados.
  • Configuração do kernel. A configuração do kernel contém Configurações DEBUG, como DEBUG_PREEMPT e DEBUG_SPIN_LOCK?
  • Programador de kernel. O kernel tem consciência de consumo de energia? programador (EAS) ou programador de vários processamentos heterogêneos (HMP)? Fazer qualquer kernel motoristas (cpu-freq, cpu-idle, cpu-hotplug etc.) afetam o programador?