loop()内の無限ループはより高速に実行されますか?


19

典型的なスケッチを書いているときloop()、Arduinoが実行されている限り、通常は繰り返し呼び出されることに依存しています。loop()ただし、関数の内外に移動すると、わずかなオーバーヘッドが発生します。

これを避けるには、おそらく次のような独自の無限ループを作成できます。

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

それはパフォーマンスを改善する実行可能な方法ですか?loop()戻らない場合、他の問題が発生しますか?

回答:


18

setup()およびloop()を行うATmegaコアのコードの部分は次のとおりです。

#include <Arduino.h>

int main(void)
{
        init();

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

        setup();

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

        return 0;
}

非常に簡単ですが、serialEventRun()のオーバーヘッドがあります。そこで。

2つの簡単なスケッチを比較してみましょう。

void setup()
{

}

volatile uint8_t x;

void loop()
{

    x = 1;

}

そして

void setup()
{

}

volatile uint8_t x;

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

xとvolatileは、最適化されていないことを確認するためのものです。

生成されたASMで、異なる結果が得られます。 2つの比較

while(true)はrjmp(相対ジャンプ)をいくつかの命令だけ実行し、loop()は減算、比較、および呼び出しを実行していることがわかります。これは4命令対1命令です。

上記のようにASMを生成するには、avr-objdumpというツールを使用する必要があります。これはavr-gccに含まれています。場所はOSによって異なるため、名前で検索するのが最も簡単です。

avr-objdumpは.hexファイルを操作できますが、これらには元のソースとコメントがありません。コードを作成したばかりの場合、このデータを含む.elfファイルが作成されます。繰り返しますが、これらのファイルの場所はOSによって異なります-それらを見つける最も簡単な方法は、設定で詳細なコンパイルをオンにして、出力ファイルが保存されている場所を確認することです。

次のようにコマンドを実行します。

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

そして、テキストエディタで出力を調べます。


わかりましたが、serialEventRun()関数を呼び出す理由はありませんか?それは何のため?
jfpoilpret 14

1
これはHardwareSerialで使用される機能の一部であり、Serialが不要なときに削除されない理由はわかりません。
サイバーギボンズ14

2
人々が自分で確認できるように、ASM出力をどのように生成したかを簡単に説明すると役立ちます。
ジッピー14

@Cyber​​gibbonsはmain.c、Arduino IDEで使用される標準の一部であるため、削除されることはありません。ただし、HardwareSerialライブラリがスケッチに含まれているわけではありません。あなたは(それを使用しない場合は、実際にそれが含まれていないことがある理由のif (serialEventRun)中でmain()機能しますが、HardwareSerialライブラリその後、使用しない場合。serialEventRunnullになります、したがって、何の呼び出しません。
jfpoilpret

1
ええ、引用されているようにmain.cの一部ですが、必要でない場合は最適化されると予想されるため、シリアルの側面は常に含まれていると思います。私は頻繁にloop()から戻らないコードを作成し、シリアルの問題に気づきません。
サイバーギボン14

6

Cyber​​gibbonsの回答は、アセンブリコードの生成と2つの手法の違いを非常にうまく説明しています。これは、実際の違い、つまりどちらのアプローチが実行時間の面でどれだけの違いをもたらすかという観点から問題を見て補完的な答えになることを意図しています。


コードバリエーション

私がやった分析以下のバリエーションが関与します:

  • 基本void loop()(コンパイル時にインライン化されます)
  • インライン化されていないvoid loop()(を使用__attribute__ ((noinline))
  • ループwhile(1)(最適化されます)
  • 最適化されていないループwhile(1)(追加することにより、__asm__ __volatile__("");これは変数のnop追加オーバーヘッドを発生させることなくループの最適化を防ぐ命令volatileです)
  • void loop()最適化された非インラインwhile(1)
  • void loop()最適化されていないインライン化されていないwhile(1)

スケッチはここにあります

実験

これらのスケッチをそれぞれ30秒間実行し、それぞれ300データポイントを蓄積しました。delay各ループで100ミリ秒の呼び出しがありました(これなければ、悪いことが起こります)。

結果

次に、各ループの平均実行時間を計算し、各ループから100ミリ秒を減算して、結果をプロットしました。

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

結論

  • while(1)内部の最適化されていないループvoid loopは、最適化されたコンパイラよりも高速です void loop
  • 最適化されていないコードとデフォルトのArduino最適化されたコードの時間差は、実際にはわずかです。avr-gccArduino IDEに依存して(マイクロ秒の最適化が必要な場合)支援するのではなく、独自の最適化フラグを使用して手動でコンパイルする方が良いでしょう。

注:ここでは、実際の時間値は重要ではありませんが、違いは重要です。実行時間の約90マイクロ秒にはSerial.printlnmicrosおよびへの呼び出しが含まれdelayます。

注2:これは、Arduino IDEとそれが提供するデフォルトのコンパイラフラグを使用して行われました。

注3:分析(プロットと計算)はRを使用して行われました。


1
よくできました。グラフにはミリ秒ではなくマイクロ秒がありますが、大きな問題はありません。
サイバーギボン14

@Cyber​​gibbonsすべての測定値がマイクロ秒単位であり、スケールをどこでも変更していないので、まったくありそうにありません:)
asheeshr 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.