Soru:
Program belleği kullanımını optimize etmenin geleneksel yolları nelerdir?
Mayoogh Girish
2020-07-01 13:34:12 UTC
view on stackexchange narkive permalink

Arduino kartları (Uno, Atmega328P MCU) kullanarak büyük projeler yaparken. Eskiden böyle uyarılar alıyordum

  Sketch 13764 bayt (% 44) program depolama alanı kullanıyor. Maksimum 30720 bayttır Küresel değişkenler 1681 bayt (% 82) dinamik bellek kullanır ve yerel değişkenler için 367 bayt bırakır. Maksimum 2048 bayttır. Düşük bellek kullanılabilir, kararlılık sorunları ortaya çıkabilir.  
  • Optimizasyon programı bellek kullanımı için genel olarak uygulanan yöntemler nelerdir?
  • Var mı? değişken global veya yerel olarak bildirilmişse bellek kullanımındaki herhangi bir fark.
  • Kontrol ifadesinin / seçim ifadelerinin ne olduğu önemli olacak mı ( if , switch )
  • Seri monitör kullanımı. Serial.print()
  • ......

Düşük bellek mevcut, kararlılık sorunları olabilir.

Bu uyarılar ne kadar kötü?

Yinelenmiş olarak işaretlemeden önce aşağıdakilere değindim. Ancak tatmin edici değildi
Belleği programlamanın en verimli yolu
Güvenli bellek kullanım sınırları nelerdir?

geleneksel bir yöntem değil, ancak Nano Every, klasik Nano'nun 3 katı SRAM'ına sahiptir ve güncellenmiş derleyici, sabit dizeleri SRAM'a kopyalamaz.
Dört yanıtlar:
Michel Keijzers
2020-07-01 13:48:19 UTC
view on stackexchange narkive permalink

Optimizasyon programı bellek kullanımı için genel olarak uygulanan yöntemler nelerdir?

Öncelikle, SRAM belleğini düşürmenin yollarını aradığınızı unutmayın. Bu, global (değişken) bellek ve yığın alanı (dinamik bellek + yığın bellek) içerir.

  • Dinamik bellek kullanmayarak bellek boşluklarından kaçının (ücretsiz / malloc / yeni ile).
  • String sınıfını kullanmaktan kaçının.
  • PROGMEM , F (..) code kullanarak SRAM'de global bellekten kaçının > mümkünse.
  • En küçük değişken boyutunu kullanın (ör. int yerine uint8_t ) .
  • Boole dizilerini bitler (bayt başına 8 boole).
  • Mümkünse bit işaretleri kullanın.
  • Dahili olarak bazı sıkıştırılmış bellek türleri kullanın (performansı etkiler), örneğin Depolayacak çok sayıda 6 bit değeriniz varsa, bunları ayrı baytlarda değil, 8 çarpı 6 bitlik değerler için 6 bayt kullanın).
  • Dizileri değere göre geçirmekten kaçının.
  • birçok (büyük) değişken içeren derin çağrı yığını. Bunun tasarımınızı etkilediğini unutmayın, bu yüzden onu son çare olarak kullanın.
  • Hesaplanabilir bir tabloya ihtiyacınız varsa, her bir değeri tablo olarak saklamak yerine hesaplayın.
  • Daha uzun kullanmayın. gerekenden daha fazla diziler oluşturun ve aksi takdirde makul maksimum dizi boyutlarını düşünün (aşağıdaki hcheung'un açıklamasına bakın).
  • (bu tam bir liste değildir).

değişken küresel veya yerel olarak bildirilirse bellek kullanımında herhangi bir fark vardır.

Evet, yerel değişkenler yığına eklenir, ancak işlev sona erdikten sonra kaldırılır, genel değişkenler kalır (ancak yalnızca Yığın üzerindeki değişkenlerin (ve ayrıca dinamik bellek) derleme sırasında uyarı mesajında ​​hesaplanan bellekte dikkate alınmadığını unutmayın.

Kontrol ifadesinin ne olduğu önemli olacak mı / seçim ifadeleri (if, switch gibi)

Hayır, bu sadece program belleğini etkileyecektir.

Seri monitör kullanımı. Serial.print ()

Muhtemelen evet, seri monitör muhtemelen (oldukça?) bir miktar belleği arabellek olarak ayırıyor.

Yetersiz bellek mevcut, kararlılık sorunları ortaya çıkabilir. Bu uyarılar ne kadar kötü?

Ne kadar kötü olduğu, ne kadar hafızanın kullanıldığına bağlıdır, bu da hesaplanmamıştır, bu dinamik hafıza ve yığın hafızadır.

Manuel olarak hesaplayabilirsiniz (bu oldukça büyük bir program için zahmetli), bunun için GitHub kitaplığını da kullanabilirsiniz:

Arduino Belleği Ücretsiz

En kötü ne kadar yığın bellek kullandığınızı biliyorsanız durumda, hesaplanan global değişkenler belleğine eklemek yerine. Bu, kullanılabilir maksimum SRAM belleğinizden azsa, güvendesiniz.

Kapsamlı özet için oy verin. Bir tane daha "dizi boyutunuzu ihtiyaca göre tahsis edin" eklemek istiyorum, sık sık birçok ArduinoJson kullanıcısının `StaticJsonDocument <1024> doc ile 1024 bayt kadar büyük bir ayırma oluşturduğunu görüyorum; bir hedef veri nesnesi için yalnızca 50 bayttan fazla almayan birkaç anahtar / değer çifti.
@hcheung ... iyi bir tane ... benim için çok fazla kullanmamak çok açık, ancak sınırlı RAM sistemiyle dizi sınırlarını dikkatlice düşünmelisiniz.
Ya #define gibi makrolar
@MayooghGirish # define'lar sadece metinsel ikamelerdir, ikamenin sonucu listemde bahsedilen örneklere uygulanabilir.
Nitpick: "Dizeyi kullanmayı engelle" yerine "Dizeyi kullanmaktan kaçının" derdim. "Kaçının", bunu yapmamanız gerektiği anlamına gelir. "Önlemek", başka birinin yapmaması için yapmanız gerektiği anlamına gelir, aksi takdirde kendi kendine olamaz. Aynı şekilde, "* sabit * global değişkenleri SRAM'de saklamaktan kaçının" (sabit kısım önemlidir, * hiçbir şeyi * PROGMEM'e taşıyamazsınız!)
@user253751 Haklısın teşekkürler, İngilizce benim ana dilim değil. Cevabımda değiştireceğim.
Kontrol etmek istersen diye birkaç değişiklik daha yaptım.
@user253751 Hepsi kabul edildi, 'eskiz' kullandım çünkü Arduino'da bu 'varsayılan' terim, ancak her yerde kullanıldığı için programı tercih etmeme rağmen.
Arduino programları eskiz olarak adlandırılır, ancak bence program belleğine hala çizim belleği değil, program belleği denir. Örneğin `PROGMEM`
@user253751 Bu doğru. Muhtemelen Arduino, bir programdan daha az 'zor' göründüğü için 'eskiz' kullanmıştır.
Edgar Bonet
2020-07-01 20:21:50 UTC
view on stackexchange narkive permalink

Michel Keijzers'ın mükemmel cevabına sadece tek bir madde eklemek istiyorum:

  • hafızanıza kaydettiğiniz her bir öğeyi düşünün ve kendinize şu soruyu sorun: Bunu gerçekten saklamam gerekiyor mu RAM'de mi?

Pek çok kişinin aşikar bulacağı şeyi söylemek aptalca gelebilir, ancak burada bunu dikkate almayan birçok acemi örneği gördük. Basit bir örnek olarak, ortalama 500 analog okuma yapan bu işlevi düşünün:

  int averageAnalogReading () {// Önce okumaları alın ve kaydedin. int okumaları [500]; (int i = 0; i < 500; i ++) okumaları için [i] = analogRead (inputPin); // Sonra ortalamayı hesaplayın. uzun toplam = 0; for (int i = 0; i < 500; i ++) toplamı + = okumalar [i]; return sum / 500;}  

Toplamı anında güncelleyebileceğiniz için tüm bu okumaları saklamak tamamen faydasızdır:

  int averageAnalogReading () {uzun toplam = 0; için (int i = 0; i < 500; i ++) toplamı + = analogRead (inputPin); return sum / 500;}  

Aynı nedenden dolayı, verileri düzeltmek için bir tür çalışan ortalamaya ihtiyacınız varsa, üssel ağırlıklı bir çalışma ortalaması kullanmayı düşünmelisiniz, bu da depolama olmadan artımlı olarak güncellenebilir orantılar.

Olumlu oy verildi, çok iyi bir nokta. İyileştirmenin en iyi yolu, algoritmaların kendisini kontrol etmektir. Kısa not: Ortalamalarla ilgileniyorsanız, yapılan her ölçüm için ek SRAM maliyeti oluşturmayan 'hareketli ortalamalara' da bakın.
@MichelKeijzers Uygun bir kutu filtre hareketli ortalama istiyorsanız, son N ölçümü kaydetmeniz gerekir. Ancak bunu üstel bir filtreye dönüştürebilirseniz, o zaman yapamazsınız.
@user253751 Bunda haklısınız. Açıklama için teşekkürler.
Artelius
2020-07-02 08:59:09 UTC
view on stackexchange narkive permalink

Optimizasyon programı bellek kullanımı için genel olarak uygulanan yöntemler nelerdir?

(nb. Edgar'ın yorumuna göre bunun PROGMEM'i daha verimli kullanmakla ilgili olduğunu vurguluyorum.)

  • Kodu, boyutu lines kod satırları olan bir tabloyla değiştirebiliyorsanız, yapın.

    • Bir ifs dizisi kullanmak yerine, prosedürü bir tabloya indirgemenin bir yolunu bulun
    • Mantıklıysa, işlev işaretçileri tablolarını kullanın
    • Bazen AVR'den çok daha yoğun bir mini dil bulabilirsiniz. talimatlar, örneğin robot mantığını 16 komuta kodlama ve ardından bayt başına iki komut paketleyebilirsiniz. Bu, bellek kullanımınızı 50 kat daraltabilir.
  • Tekrarlanan kod yerine işlevleri kullanın — bu apaçık görünebilir, ancak genellikle kodu yeniden yazmanın ince yolları vardır (ancak bu işlev çağrılarının ek yükü vardır)
  • Büyük boşluklara sahip tablolar yerine karma tablolar kullanın
  • Kayan nokta yerine sabit nokta kullanın (örneğin, bir bayt alıp değerini, 4 baytlık kayan nokta kullanmak yerine 0,00 ila 2,55)

Değişken global veya yerel olarak bildirilmişse bellek kullanımında herhangi bir fark var mı?

Yığından bahsedelim.

  void A () {byte a [600]; ...} void B () {bayt b [400]; ...} boşluk döngüsü () {bayt xxx [1000]; ...}  

Bu program öncelikle her zaman en az 1000 bayt RAM kullanacaktır. Global olarak xxx bildirmekle karşılaştırıldığında gerçek bir fark yoktur. Ama o zaman kritik olan, hangi işlevi çağırdığıdır.

Eğer loop () A () 'yı çağırırsa ve sonra loop () B ()' yi çağırırsa, program hiçbir zaman 1600'den fazla kullanmayacaktır. Bununla birlikte, eğer A () B () 'yi çağırırsa veya tersi olursa, program 2000 kullanır. Göstermek için:

  loop () [1000] └── ── A () [1600] │ [1000] └──── B () [1400] └──── A () [1600] └──── B () [1400]  

ile

  loop () [1000] └──── A () [1600]
└──── B () [2000] │ [1000] └──── A () [1600] └──── B () [2000]  

Kontrol ifadesinin / seçim ifadelerinin ne olduğu önemli mi (if, switch gibi)

Az sayıda durum için pek bir fark yok. Aksi takdirde kodunuza bağlıdır. En iyi yol, ikisini birden denemek ve hangisinin daha iyi olduğunu görmektir. Ancak:

anahtar ları genellikle, bir aralıktaki neredeyse her durumu (0,1,2,3,4, .., 100) kapsıyorsanız oldukça kompakt olan atlama tabloları kullanır. ). if 'ler genellikle, bir atlama tablosu girişinden daha fazla bayt ve döngü alan bir dizi talimat kullanır, ancak ardışık bir vakaya sahip değilseniz bu daha anlamlıdır.

Seri monitör kullanımı. Serial.print ()

Bunun pek fark yarattığına inanmıyorum. Seri tamponlar çok küçüktür (daha büyük bir pano için 64 bayt veya 128 diyelim) ve Seri kullansanız da kullanmasanız da tahsis edildiğine inanıyorum.

Elbette "bunun gibi birebir dizgiler" ve char [] tamponları hafızayı tüketir. İhtiyaç duymadığınız zamanlarda yorum yapabilir (veya #ifdef ’leri kullanabilirsiniz).

1. İlk 5 madde işaretinizin RAM'den tasarruf etmek yerine flash tasarrufu yapmakla ilgili olduğunu unutmayın. OP flaş konusunda kısa değildi. PROGMEM'e koymadığınız sürece tablolar aslında _cost_ RAM olacaktır. 2. Re “_Xxx'i global olarak bildirmeye kıyasla gerçek bir fark yoktur”: yani, “setup ()” hafızaya aç olmadığı sürece. 3. "Seri" kullanmıyorsanız seri arabellekler tahsis edilmez.
Teşekkürler @EdgarBonet 1. Evet, PROGMEM düşünüyordum, üzgünüm. 2. Bunu açıklar mısınız? `Setup () 'ın yığın ayrılan belleği çalıştırıldıktan sonra serbest bırakılmaz mı? 3. Teşekkürler bunu düzenledim.
2. noktayı yeniden açıklayın: "setup ()" ve "loop ()" genellikle aynı anda çalışmaz, bu nedenle yığın kullanımları toplanmaz. "Xxx" yi global yaparsanız, "setup ()" çalışırken bile tahsis edilecektir. Bu, `setup () '' ın kendisi belleğe aç olmadığı sürece endişelenmemelidir.
David G.
2020-07-03 18:34:02 UTC
view on stackexchange narkive permalink

Geleneksel yolları sorduğunuz için, geleneksel bir yöntem ortaya atacağım. Bu durumda, 50 yıldan daha eski.

Bir liste oluşturun ve analiz edin.

Metodoloji:

  1. Tüm kodu şu şekilde derleyin: hata ayıklama açık (-g ekleyin).

  2. Kodu hata ayıklama açıkken bağlayarak bir ELF yürütülebilir dosyası oluşturun. Arduino'da yüklenebilir bir görüntüye DÖNÜŞTÜRMEYİN.

  3. Liste oluşturmak için objdump kullanın. Bunu kullanımım şu şekilde:

      (avr-objdump --headers --source --disassemble --syms program.elf; \ avr-objdump --full-content --section =. final_progmem program.elf) > program.lst  

    Kullanımınız, görmeyi tercih ettiğiniz şeye bağlı olarak değişebilir.

bu, hafızanızın her baytını tam olarak neyin kullandığını görmenizi sağlar.

Ne gördüğünüzü anlayana kadar farklı optimizasyonlarla oynamak isteyebilirsiniz. -O0 en anlaşılır demontajı üretirken, -Os en küçüğü yapar.

Bunu yapmanıza dayalı bir ipucu: Arduino kitaplıkları aşağıdakiler için tasarlanmıştır: genellik, hız ve bellek verimliliği değil.



Bu Soru-Cevap, otomatik olarak İngilizce dilinden çevrilmiştir.Orijinal içerik, dağıtıldığı cc by-sa 4.0 lisansı için teşekkür ettiğimiz stackexchange'ta mevcuttur.
Loading...