Papan Arduino Uno memiliki RAM terbatas yang berarti memiliki tumpukan panggilan terbatas. Terkadang, rekursi adalah satu-satunya pilihan cepat untuk mengimplementasikan algoritma tertentu. Jadi, mengingat bahwa tumpukan panggilan sangat terbatas, apa yang akan menjadi cara untuk mengetahui bahwa mengingat program tertentu berjalan di papan tulis, persis berapa banyak panggilan rekursif yang Anda mampu sebelum ada tumpukan melimpah (dan hal-hal buruk terjadi)?
programming
sram
asheeshr
sumber
sumber
How much ca!@#QFSD@$RFW
? Saya ingin tahu mengapa tidak ada yang mengedit itu menjadi sesuatu yang lebih bermakna (dalam 4 tahun terakhir).211
waktu (tergantung banyak faktor) :). Lihat jawaban saya di sini: arduino.stackexchange.com/a/51098/7727 . @NickGammon, dia berpura-pura "mengutuk", saya pikir. Ini adalah permainan kata untuk "recurse". Butuh waktu sebentar untuk memikirkannya juga. Awalnya cukup membingungkan.Jawaban:
Jika Anda benar-benar ingin muncul kembali (dan seperti yang dikatakan @jippie itu adalah ide yang buruk; pesan bawah sadar: jangan lakukan itu ) dan ingin tahu seberapa banyak Anda dapat berulang, maka Anda harus melakukan beberapa perhitungan dan percobaan; Anda juga biasanya hanya akan memiliki perkiraan karena tergantung banyak pada kondisi memori pada saat fungsi rekursif Anda akan dipanggil.
Untuk ini, Anda harus terlebih dahulu tahu bagaimana SRAM diatur dalam Arduino berbasis AVR (itu tidak akan berlaku untuk misalnya Arduino Galileo oleh Intel). Diagram berikut dari Adafruit menunjukkannya dengan jelas:
Maka Anda perlu mengetahui ukuran total SRAM Anda (tergantung pada Atmel MCU, maka jenis papan Arduino yang Anda miliki).
Pada diagram ini, mudah untuk mengetahui ukuran blok Data Statis seperti yang diketahui pada waktu kompilasi dan tidak akan berubah nanti.
The Heap Ukuran dapat lebih sulit untuk mengetahui karena dapat bervariasi pada saat runtime, tergantung pada alokasi memori dinamis (
malloc
ataunew
) dilakukan oleh sketsa atau perpustakaan menggunakan. Menggunakan memori dinamis sangat jarang di Arduino, tetapi beberapa fungsi standar melakukannya (String
saya pikir itu menggunakan tipe itu).Untuk ukuran Stack , ini juga akan bervariasi selama runtime, berdasarkan pada kedalaman panggilan fungsi saat ini (setiap panggilan fungsi membutuhkan 2 byte pada Stack untuk menyimpan alamat pemanggil) dan jumlah dan ukuran variabel lokal termasuk argumen yang diteruskan ( yang juga disimpan di Stack ) untuk semua fungsi yang dipanggil sampai sekarang.
Jadi anggaplah
recurse()
fungsi Anda menggunakan 12 byte untuk variabel dan argumen lokalnya, maka setiap panggilan ke fungsi ini (yang pertama dari penelepon eksternal dan yang rekursif) akan menggunakan12+2
byte.Jika kita mengira bahwa:
recurse()
fungsi Anda dipanggil dari sketsa Anda, tumpukan saat ini adalah 128 byteKemudian Anda dibiarkan dengan
2048 - 132 - 128 = 1788
byte yang tersedia di Stack . Dengan demikian, jumlah panggilan rekursif ke fungsi Anda1788 / 14 = 127
, termasuk panggilan awal (yang bukan merupakan panggilan rekursif).Seperti yang Anda lihat, ini sangat sulit, tetapi bukan tidak mungkin untuk menemukan apa yang Anda inginkan.
Cara yang lebih sederhana untuk mendapatkan ukuran tumpukan tersedia sebelum
recurse()
dipanggil adalah dengan menggunakan fungsi berikut (ditemukan di pusat pembelajaran Adafruit; saya belum mengujinya sendiri):Saya sangat menyarankan Anda untuk membaca artikel ini di pusat pembelajaran Adafruit.
sumber
.bss
mewakili variabel global tanpa nilai awal dalam kode Anda, sedangkandata
untuk variabel global dengan nilai awal. Tetapi pada akhirnya mereka menggunakan ruang yang sama: Data Statis dalam diagram.static
dalam suatu fungsi.Rekursi adalah praktik buruk pada mikrokontroler karena Anda telah menyatakan diri Anda dan Anda mungkin ingin menghindarinya kapan pun memungkinkan. Di situs Arduino ada beberapa contoh dan perpustakaan yang tersedia untuk memeriksa ukuran RAM gratis . Misalnya Anda dapat menggunakan ini untuk mencari tahu kapan harus istirahat rekursi atau sedikit rumit / berisiko untuk profil sketsa Anda dan kode keras batas di dalamnya. Profil ini diperlukan untuk setiap perubahan dalam program Anda dan untuk setiap perubahan dalam rantai alat Arduino.
sumber
Tergantung fungsinya.
Setiap kali fungsi dipanggil, bingkai baru didorong ke tumpukan. Biasanya akan berisi berbagai item penting, berpotensi termasuk:
this
) jika memanggil fungsi anggota.Seperti yang Anda lihat, ruang tumpukan yang diperlukan untuk panggilan yang diberikan tergantung pada fungsinya. Misalnya, jika Anda menulis fungsi rekursif yang hanya mengambil
int
parameter dan tidak menggunakan variabel lokal, itu tidak akan membutuhkan lebih dari beberapa byte pada stack. Itu berarti Anda dapat menyebutnya secara rekursif lebih dari fungsi yang mengambil beberapa parameter dan menggunakan banyak variabel lokal (yang akan memakan tumpukan lebih cepat).Jelas keadaan tumpukan tergantung pada apa lagi yang terjadi dalam kode. Jika Anda memulai rekursi langsung di dalam
loop()
fungsi standar , maka kemungkinan tidak akan ada banyak pada stack. Namun, jika Anda memulainya bersarang beberapa level dalam fungsi lain, maka tidak akan ada banyak ruang. Itu akan mempengaruhi berapa kali Anda bisa kambuh tanpa menguras tumpukan.Perlu dicatat bahwa optimasi rekursi ekor ada pada beberapa kompiler (walaupun saya tidak yakin apakah avr-gcc mendukungnya). Jika panggilan rekursif adalah hal terakhir dalam suatu fungsi, itu berarti kadang-kadang mungkin untuk menghindari mengubah bingkai tumpukan sama sekali. Compiler hanya dapat menggunakan kembali bingkai yang ada, karena panggilan 'induk' (jadi untuk berbicara) selesai menggunakannya. Itu berarti Anda secara teoritis dapat terus berulang sebanyak yang Anda suka, selama fungsi Anda tidak memanggil yang lain.
sumber
Saya memiliki pertanyaan yang sama persis seperti saya membaca Jumping into C ++ oleh Alex Allain , Bab 16: Rekursi, hal.230, jadi saya menjalankan beberapa tes.
TLDR;
Arduino Nano saya (ATmega328 mcu) dapat melakukan 211 panggilan fungsi rekursif (untuk kode yang diberikan di bawah ini) sebelum memiliki stack overflow dan crash.
Pertama, izinkan saya menangani klaim ini:
[Pembaruan: ah, saya membaca sepintas kata "cepat". Dalam hal ini Anda memiliki validitas. Namun demikian, saya pikir ada baiknya mengatakan hal berikut.]
Tidak, saya tidak berpikir itu pernyataan yang benar. Saya cukup yakin semua algoritma memiliki solusi rekursif dan non-rekursif, tanpa kecuali. Hanya saja kadang-kadang jauh lebih mudahuntuk menggunakan algoritma rekursif. Karena itu, rekursi sangat disukai untuk digunakan pada mikrokontroler dan mungkin tidak akan pernah diizinkan dalam kode keamanan kritis. Meskipun demikian, tentu saja dimungkinkan untuk melakukannya pada mikrokontroler. Untuk mengetahui seberapa "dalam" Anda bisa masuk ke fungsi rekursif yang diberikan, cukup mengujinya! Jalankan dalam aplikasi kehidupan nyata Anda dalam test case kehidupan nyata, dan lepaskan kondisi dasar Anda sehingga akan terulang kembali. Cetak penghitung dan lihat sendiri seberapa "dalam" Anda sehingga Anda tahu apakah algoritma rekursif Anda mendorong batas RAM Anda terlalu dekat untuk digunakan secara praktis. Berikut adalah contoh di bawah ini untuk memaksa stack overflow pada Arduino.
Sekarang, beberapa catatan:
Berapa banyak panggilan rekursif, atau "stack frames" yang bisa Anda dapatkan ditentukan oleh sejumlah faktor, termasuk:
free_RAM = total_RAM - stack_used - heap_used
atau Anda mungkin mengatakanfree_RAM = stack_size_allocated - stack_size_used
)Hasil saya:
Segmentation fault (core dumped)
#pragma GCC optimize ("-O0")
ke bagian atas file dan ulangi:Here are the final print results: 209 210 211 ⸮ 9⸮ 3⸮
Kode:
Aplikasi PC:
Program "Sketsa" Arduino:
Referensi:
#pragma GCC optimize
perintah karena saya tahu saya sudah didokumentasikan di sana.sumber
#pragma
tidak menggunakannya di sana. Sebagai gantinya, Anda dapat menambahkan__attribute__((optimize("O0")))
ke fungsi tunggal yang ingin Anda optimalkan.Saya menulis program tes sederhana ini:
Saya mengkompilasinya untuk Uno, dan ketika saya menulisnya telah berulang lebih dari 1 juta kali! Saya tidak tahu, tetapi kompiler mungkin telah mengoptimalkan program ini
sumber
call xxx
/ret
olehjmp xxx
. Ini sama dengan jumlah yang sama, kecuali bahwa metode kompiler tidak mengkonsumsi stack. Dengan demikian Anda dapat kambuh miliaran kali dengan kode Anda (hal lain dianggap sama).#pragma GCC optimize ("-O0")
bagian atas program Arduino Anda. Saya percaya Anda harus melakukan ini di bagian atas setiap file yang Anda inginkan untuk diterapkan - tetapi saya belum melihat itu selama bertahun-tahun jadi riset untuk Anda sendiri untuk memastikan.