Apakah loop infinite di dalam loop () berkinerja lebih cepat?

19

Saat Anda menulis sketsa yang khas, Anda biasanya mengandalkan loop()pemanggilan berulang kali selama Arduino berjalan. Bergerak masuk dan keluar dari loop()fungsi harus memperkenalkan overhead kecil.

Untuk menghindari itu, Anda mungkin bisa membuat loop tak terbatas Anda sendiri, seperti ini:

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

Apakah itu cara yang layak untuk meningkatkan kinerja? Apakah akan menimbulkan masalah lain jika loop()tidak pernah kembali?

Peter Bloomfield
sumber

Jawaban:

18

Bagian dari kode pada inti ATmega yang melakukan setup () dan loop () adalah sebagai berikut:

#include <Arduino.h>

int main(void)
{
        init();

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

        setup();

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

        return 0;
}

Cukup sederhana, tetapi ada overhead serialEventRun (); di sana.

Mari kita bandingkan dua sketsa sederhana:

void setup()
{

}

volatile uint8_t x;

void loop()
{

    x = 1;

}

dan

void setup()
{

}

volatile uint8_t x;

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

X dan volatile hanya untuk memastikan tidak dioptimalkan.

Di ASM yang diproduksi, Anda mendapatkan hasil yang berbeda: Perbandingan dua

Anda dapat melihat while (true) hanya melakukan rjmp (lompatan relatif) kembali beberapa instruksi, sedangkan loop () melakukan pengurangan, perbandingan, dan panggilan. Ini adalah 4 instruksi vs 1 instruksi.

Untuk menghasilkan ASM seperti di atas, Anda perlu menggunakan alat yang disebut avr-objdump. Ini termasuk dengan avr-gcc. Lokasi bervariasi tergantung pada OS sehingga paling mudah untuk mencarinya dengan nama.

avr-objdump dapat beroperasi pada file .hex, tetapi ini tidak memiliki sumber asli dan komentar. Jika Anda baru saja membuat kode, Anda akan memiliki file .elf yang memang berisi data ini. Sekali lagi, lokasi file-file ini bervariasi menurut OS - cara termudah untuk menemukannya adalah dengan mengaktifkan kompilasi verbose dalam preferensi dan melihat di mana file output disimpan.

Jalankan perintah sebagai berikut:

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

Dan periksa hasilnya dalam editor teks.

Cybergibbons
sumber
OK, tapi bukankah ada alasan untuk memanggil fungsi serialEventRun ()? Untuk apa ini?
jfpoilpret
1
Ini bagian dari fungsionalitas yang digunakan oleh HardwareSerial, tidak yakin mengapa itu tidak dihapus ketika Serial tidak diperlukan.
Cybergibbons
2
Akan sangat membantu untuk menjelaskan secara singkat bagaimana Anda menghasilkan output ASM sehingga orang dapat memeriksa sendiri.
jippie
@Cybergibbons tidak pernah dikeluarkan karena ini adalah bagian dari standar yang main.cdigunakan oleh Arduino IDE. Namun itu tidak berarti bahwa pustaka HardwareSerial disertakan ke sketsa Anda; sebenarnya itu tidak termasuk jika Anda tidak menggunakannya (itu sebabnya ada if (serialEventRun)dalam main()fungsi. Jika Anda tidak menggunakan perpustakaan HardwareSerial maka serialEventRunakan menjadi nol, maka tidak ada panggilan.
jfpoilpret
1
Ya, itu adalah bagian dari main.c seperti dikutip, tapi saya berharap itu akan dioptimalkan jika tidak diperlukan maka saya pikir aspek Serial selalu disertakan. Saya sering menulis kode yang tidak akan pernah kembali dari loop () dan tidak melihat masalah dengan Serial.
Cybergibbons
6

Jawaban Cybergibbons menggambarkan dengan cukup baik pembuatan kode perakitan dan perbedaan di antara kedua teknik. Ini dimaksudkan sebagai jawaban yang melengkapi melihat masalah dalam hal perbedaan praktis , yaitu berapa banyak perbedaan yang akan dibuat oleh kedua pendekatan dalam hal waktu eksekusi .


Variasi Kode

Saya melakukan analisis yang melibatkan variasi berikut:

  • Basic void loop()(yang dimasukkan pada kompilasi)
  • Tidak bergaris void loop()(menggunakan __attribute__ ((noinline)))
  • Loop dengan while(1)(yang dioptimalkan)
  • Loop dengan un-dioptimalkan while(1)(dengan menambahkan __asm__ __volatile__("");. Ini adalah nopinstruksi yang mencegah optimalisasi loop tanpa menghasilkan overhead volatilevariabel tambahan)
  • Tidak-sebaris void loop()dengan dioptimalkanwhile(1)
  • Tidak-sebaris void loop()dengan tidak-dioptimalkanwhile(1)

Sketsa dapat ditemukan di sini .

Percobaan

Saya menjalankan masing-masing sketsa ini selama 30 detik, sehingga masing-masing mengumpulkan 300 titik data . Ada delaypanggilan 100 milidetik di setiap loop (tanpanya hal buruk terjadi ).

Hasil

Saya kemudian menghitung rata-rata waktu eksekusi dari setiap loop, mengurangi 100 milidetik dari masing-masing dan kemudian merencanakan hasilnya.

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

Kesimpulan

  • while(1)Loop yang tidak dioptimalkan void looplebih cepat daripada kompiler yang dioptimalkan void loop.
  • Perbedaan waktu antara kode yang tidak dioptimalkan dan kode optimal yang dioptimalkan Arduino secara praktis tidak signifikan . Anda akan lebih baik mengkompilasi secara manual menggunakan avr-gccdan menggunakan flag optimasi Anda sendiri daripada bergantung pada Arduino IDE untuk membantu Anda dengan itu (jika Anda memerlukan optimasi mikrodetik).

CATATAN: Nilai waktu aktual tidak signifikan di sini, perbedaannya adalah. ~ 90 mikrodetik waktu eksekusi mencakup panggilan ke Serial.println, microsdan delay.

CATATAN2: Ini dilakukan dengan menggunakan Arduino IDE dan flag-flag compiler default yang disediakannya .

CATATAN3: Analisis (plot dan perhitungan) dilakukan dengan menggunakan R.

asheeshr
sumber
1
Kerja bagus. Grafik memiliki milidetik bukan mikrodetik tetapi bukan masalah besar.
Cybergibbons
@Cybergibbons Thats sangat tidak mungkin karena semua pengukuran dalam mikrodetik dan saya tidak mengubah timbangan 'di mana saja :)
asheeshr