Um loop infinito dentro de loop () funcionaria mais rápido?

19

Quando você está escrevendo um esboço típico, geralmente depende de loop()ser chamado repetidamente enquanto o Arduino estiver em execução. A entrada e saída da loop()função deve introduzir uma pequena sobrecarga.

Para evitar isso, você pode criar seu próprio loop infinito, como este:

void loop()
{
    while (true)
    {
        // do stuff...
    }
}

Essa é uma maneira viável de melhorar o desempenho? Causará outros problemas se loop()nunca voltar?

Peter Bloomfield
fonte

Respostas:

18

A parte do código em um núcleo ATmega que executa setup () e loop () é a seguinte:

#include <Arduino.h>

int main(void)
{
        init();

#if defined(USBCON)
        USBDevice.attach();
#endif

        setup();

        for (;;) {
                loop();
                if (serialEventRun) serialEventRun();
        }

        return 0;
}

Muito simples, mas há a sobrecarga do serialEventRun (); lá.

Vamos comparar dois esboços simples:

void setup()
{

}

volatile uint8_t x;

void loop()
{

    x = 1;

}

e

void setup()
{

}

volatile uint8_t x;

void loop()
{
    while(true)
    {
        x = 1;
    }
}

O x e volátil é apenas para garantir que não seja otimizado.

No ASM produzido, você obtém resultados diferentes: Comparação de dois

Você pode ver o tempo (true) apenas executando um rjmp (salto relativo) de volta algumas instruções, enquanto o loop () realiza uma subtração, comparação e chamada. São 4 instruções vs 1 instrução.

Para gerar o ASM como acima, você precisa usar uma ferramenta chamada avr-objdump. Isso está incluído no avr-gcc. A localização varia de acordo com o sistema operacional, portanto, é mais fácil procurar por nome.

O avr-objdump pode operar em arquivos .hex, mas estes estão ausentes da fonte e dos comentários originais. Se você acabou de criar código, terá um arquivo .elf que contém esses dados. Novamente, a localização desses arquivos varia de acordo com o sistema operacional - a maneira mais fácil de localizá-los é ativar a compilação detalhada nas preferências e ver onde os arquivos de saída estão sendo armazenados.

Execute o comando da seguinte maneira:

avr-objdump -S output.elf> asm.txt

E examine a saída em um editor de texto.

Cybergibbons
fonte
OK, mas não há um motivo para chamar a função serialEventRun ()? Para que serve?
Jfpoilpret
1
Faz parte da funcionalidade usada pelo HardwareSerial, não sabe por que não é retirada quando o Serial não é necessário.
Cybergibbons
2
Seria útil explicar brevemente como você gerou a saída ASM para que as pessoas possam verificar a si mesmas.
jippie
@ Cyybibbons nunca é retirado porque faz parte do padrão main.cusado pelo IDE do Arduino. No entanto, isso não significa que a biblioteca HardwareSerial esteja incluída no seu esboço; na verdade, ele não está incluído se você não usá-lo (é por isso que existe if (serialEventRun)em main()função Se você não usar HardwareSerial biblioteca de então. serialEventRunserá nulo, portanto, nenhuma chamada.
jfpoilpret
1
Sim, faz parte do main.c, conforme citado, mas eu esperaria que fosse otimizado se não fosse necessário, portanto, acho que os aspectos do Serial estão sempre incluídos. Frequentemente escrevo código que nunca retornará de loop () e não percebo problemas com o Serial.
precisa saber é o seguinte
6

A resposta de Cybergibbons descreve muito bem a geração de código de montagem e as diferenças entre as duas técnicas. Pretende-se que seja uma resposta complementar que analise a questão em termos de diferenças práticas , ou seja, quanta diferença uma das abordagens fará em termos de tempo de execução .


Variações de código

Fiz uma análise envolvendo as seguintes variações:

  • Básico void loop()(incluído na compilação)
  • Não alinhado void loop()(usando __attribute__ ((noinline)))
  • Loop com while(1)(que é otimizado)
  • Loop com não otimizado while(1)(adicionando __asm__ __volatile__("");. Esta é uma nopinstrução que impede a otimização do loop sem resultar em sobrecargas adicionais de uma volatilevariável)
  • Um não alinhado void loop()com otimizadowhile(1)
  • Um não alinhado void loop()com não otimizadowhile(1)

Os esboços podem ser encontrados aqui .

Experimentar

Executei cada um desses esboços por 30 segundos, acumulando 300 pontos de dados cada . Havia uma delaychamada de 100 milissegundos em cada loop (sem que coisas ruins acontecessem ).

Resultados

Calculei os tempos médios de execução de cada loop, subtraí 100 milissegundos de cada um e plotei os resultados.

http://raw2.github.com/AsheeshR/Arduino-Loop-Analysis/master/Figures/timeplot.png

Conclusão

  • Um while(1)loop não otimizado void loopé mais rápido do que um compilador otimizado void loop.
  • A diferença de tempo entre o código não otimizado e o código otimizado padrão do Arduino é praticamente insignificante . É melhor compilar manualmente usando avr-gcce usando seus próprios sinalizadores de otimização em vez de depender do IDE do Arduino para ajudá-lo (se você precisar de otimizações de microssegundos).

NOTA: Os valores de tempo reais não são significativos aqui, a diferença entre eles é. Os ~ 90 microssegundos de tempo de execução incluem uma chamada para Serial.println, microse delay.

NOTA2: Isso foi feito usando o Arduino IDE e os sinalizadores padrão do compilador que ele fornece.

NOTA3: A análise (plot e cálculos) foi realizada usando R.

asheeshr
fonte
1
Bom trabalho. O gráfico possui milissegundos, não microssegundos, mas não um grande problema.
Cybergibbons
Isso é @Cybergibbons bastante improvável, pois todas as medições estão em microssegundos e eu não mudei qualquer lugar escalas :)
asheeshr