Sobre a questão da velocidade e medição no Arduino



Esse problema surgiu no estudo do desempenho do Arduino ao executar vários comandos (mais sobre isso em um post separado). No decorrer do estudo, surgiram dúvidas sobre a constância do tempo de operação de comandos individuais ao alterar o valor dos operandos (como se viu posteriormente, não razoável) e foi tomada uma decisão para tentar estimar o tempo de execução de um comando individual. Para isso, foi elaborado um pequeno programa (que dizia que o desenho deveria sair da classe), o qual, à primeira vista, confirmou a hipótese. Na conclusão, você pode observar os valores 16 e 20, mas às vezes 28 e até 32 microssegundos são encontrados. Se multiplicarmos os dados recebidos por 16 (freqüência do relógio MK), obteremos o tempo de execução em ciclos MK (de 256 a 512). Infelizmente, uma execução repetida do ciclo principal do programa (com os mesmos dados iniciais), mantendo a imagem geral, já fornece uma distribuição diferente do tempo de execução; portanto, as variações reais de tempo não estão relacionadas aos dados iniciais. A hipótese original é refutada, mas se torna interessante e o que exatamente está associado a uma dispersão tão significativa.

Nota necessária - Eu entendo muito bem que programas mais sofisticados devem ser usados ​​para medir o tempo de execução dos comandos, mas para uma estimativa aproximada, um que será demonstrado mais tarde é suficiente.

Então, o tempo muda e, de maneira muito significativa, estamos procurando as causas desse fenômeno. Antes de tudo, prestamos atenção à multiplicidade dos valores obtidos, observamos a descrição da biblioteca de trabalhos ao longo do tempo e observamos que 4µseg é um quantum de medida, então é melhor ir ao quanta e entender que obtemos 4 ou 5 (com muita frequência) e 6 ou 7 ou 8 (muito raras) unidades. Com a primeira metade, tudo é fácil - se o valor medido estiver entre 4 e 5 unidades, a dispersão se tornará inevitável. Além disso, considerando que as amostras são independentes, podemos aumentar a precisão da medição por métodos estatísticos, que é o que fazemos, obtendo resultados aceitáveis.

Mas com a segunda metade (6,7,8) as coisas são piores. Descobrimos que a dispersão não se correlaciona com os dados de origem, o que significa que essa é uma manifestação de outros processos que afetam o tempo de execução dos comandos. Note-se que as emissões são bastante raras e não mostram um efeito significativo no valor médio calculado. Seria possível negligenciá-los, mas esse não é o nosso estilo. Em geral, ao longo dos anos de trabalho em engenharia, percebi que você não pode deixar mal-entendidos, por mais insignificantes que pareçam, pois eles têm uma capacidade repugnante de bater nas costas (bem, ou onde mais eles alcançam) no momento mais inoportuno.

Começamos a apresentar a hipótese 1 - a mais conveniente (por conveniência e versatilidade, perdendo apenas para a intervenção direta do Criador) - falhas de software, é claro, não as minhas, meus programas nunca falham, mas as bibliotecas conectadas (compilador, sistema operacional, navegador etc.) - substitua o necessário). Além disso, como eu executo o programa no emulador em www.tinkercad.com , você ainda pode consultar os erros do emulador e fechar o tópico, porque as fontes não estão disponíveis para nós. Contras desta hipótese:

  1. De ciclo para ciclo, a localização dos desvios muda, o que sugere.
  2. Este site ainda suporta o AutoDesk, embora o argumento seja bastante fraco.
  3. "Aceitamos o postulado de que o que está acontecendo não é uma alucinação; caso contrário, seria simplesmente desinteressante".

A próxima suposição é a influência de alguns processos em segundo plano no resultado da medição. Parece que não estamos fazendo nada além de acreditar ... estamos produzindo os resultados em série. A hipótese 2 surge - o tempo de saída às vezes (por mais estranho que isso ... mas acontece) é adicionado ao tempo de execução do comando. Embora seja duvidoso quanto essa saída existe, de qualquer maneira - adicionar Flush não ajudou, adicionar um atraso para terminar a saída e não ajudou, geralmente movendo a saída para fora do loop - de qualquer forma, o tempo aumenta - isso definitivamente não é serial.

Bem, o que resta é a organização do próprio ciclo (do qual é assustador mudar sua duração, não está claro) e isso é tudo ... apesar de micros () permanecerem. Eu quis dizer que o tempo de execução da primeira chamada desta função e da segunda é o mesmo e, ao subtrair esses dois valores, eu recebo zero, mas se essa suposição estiver errada?

Hipótese 3 - às vezes a segunda chamada da contagem do tempo demora mais que a primeira, ou as ações associadas à contagem do tempo às vezes afetam o resultado. Examinamos o código fonte da função de trabalhar com o tempo (arduino-1.8.4 \ hardware \ arduino \ avr \ núcleos \ arduino \ fiação.c - expressei repetidamente minha atitude em relação a essas coisas, não vou me repetir) e vemos que 1 em cada 256 ciclos de aumento de hardware da parte mais jovem do contador são interrompidos para incrementar a parte mais antiga do contador.

Nosso tempo de execução do ciclo é de 4 a 5, portanto, podemos esperar 170 * (4..5) / 256 = de três a quatro valores anômalos em um segmento de 170 medições. Nós olhamos - parece muito semelhante, existem realmente quatro deles. Para separar a primeira e a segunda razões, fazemos cálculos pela seção crítica com interrupções proibidas. O resultado não muda muito, as emissões ainda têm um lugar para se estar, o que significa que o tempo extra é gerado por micros (). Aqui não podemos fazer nada, embora o código fonte esteja disponível, não podemos alterá-lo - as bibliotecas estão incluídas nos binários. Obviamente, podemos escrever nossas próprias funções de trabalhar com o tempo e observar seu comportamento, mas existe uma maneira mais simples.

Como uma possível razão para o aumento da duração é o processamento de interrupção “longo”, excluímos a possibilidade de sua ocorrência durante o processo de medição. Para fazer isso, aguarde sua manifestação e só então realizamos um ciclo de medição. Como a interrupção ocorre com muito menos frequência do que o nosso ciclo de medição, podemos garantir sua ausência. Escrevemos o fragmento correspondente do programa (usando hacks sujos com informações extraídas do código-fonte) e, “isso é mágica de rua”, tudo se torna normal - medimos o tempo de execução de 4 e 5 quanta com um valor médio do tempo de execução da operação de adição com PT de 166 ciclos de relógio, o que corresponde ao valor medido anteriormente. A hipótese pode ser considerada confirmada.

Resta mais uma pergunta - e o que leva tanto tempo em interrupções, o que é preciso
(7.8) - (5) ~ 2 quanta = * 4 = 8mseg * 16 = 128 ciclos do processador? Voltamos ao código fonte (ou seja, ao código assembler gerado pelo compilador em godbolt.com) e vemos que a própria interrupção é executada em aproximadamente 70 ciclos, 60 deles constantemente, e ao ler, existem custos adicionais de 10 ciclos, total de 70 quando atingidos. interrupção - menos do que recebido, mas perto o suficiente. Atribuímos a diferença à diferença entre compiladores ou modos de uso.

Bem, agora podemos medir o tempo de execução real do comando de adição PT com vários argumentos e garantir que ele realmente mude muito quando os argumentos mudam: de 136 mede para 0,0 a 190 para 0,63 (número mágico), e isso é apenas 162 para 10,63. Com uma probabilidade de 99,9%, isso se deve à necessidade de alinhamento e aos recursos de sua implementação nessa biblioteca específica, mas este estudo claramente vai além do escopo do problema em consideração.

Apêndice - texto do programa:
void setup() { Serial.begin(9600); } volatile float t; //   void loop() { int d[170]; unsigned long time,time1; float dt=1/170.; for (int i=0; i<170; ++i) { { //       time1=micros(); long time2; do { time2=micros(); } while ((time2 & ~0xFF) == (time1 & ~0xFF)); }; /**/ time1=micros(); //   /* cli(); //       -   */ t=10.63; //     t=t+dt; //   /* sei(); //    */ time = micros(); //   time1=time-time1; d[i]=time1/4; /* Serial.print(time1); //      Serial.flush(); //     Delay(20); //    */ }; //   ,     float sum=0; for (int i=0; i<170; ++i) { sum+=d[i]; Serial.println(d[i]); }; Serial.println((sum/170-2.11)*4*16); //2.11Serial.flush(); //    ,     } 

Source: https://habr.com/ru/post/pt426941/


All Articles