Saya menggunakan terlalu banyak RAM. Bagaimana ini bisa diukur?

19

Saya ingin tahu berapa banyak RAM yang saya gunakan dalam proyek saya, sejauh yang saya tahu, tidak ada cara untuk benar-benar menyelesaikannya (selain melalui dan menghitung sendiri). Saya telah sampai pada tahap dalam proyek yang agak besar di mana saya telah menentukan bahwa saya kehabisan RAM.

Saya telah menentukan ini karena saya dapat menambahkan satu bagian dan kemudian semua kacau di tempat lain dalam kode saya tanpa alasan yang jelas. Jika saya melakukan #ifndefsesuatu yang lain, itu akan berhasil lagi. Tidak ada yang salah secara pemrograman dengan kode baru.

Saya menduga untuk sementara waktu bahwa saya sampai pada akhir RAM yang tersedia. Saya tidak berpikir saya menggunakan terlalu banyak tumpukan (walaupun itu mungkin), apa cara terbaik untuk menentukan berapa banyak RAM yang sebenarnya saya gunakan?

Melewati dan mencoba menyelesaikannya, saya memiliki masalah ketika saya mendapatkan enum dan struct; berapa memori yang biayanya?

edit pertama : JUGA, saya telah mengedit sketsa saya begitu banyak sejak mulai, ini bukan hasil sebenarnya yang saya dapatkan pada awalnya, tetapi itu adalah apa yang saya dapatkan sekarang.

  text     data     bss     dec     hex filename
 17554      844     449   18847    499f HA15_20140317w.cpp.elf
 16316      694     409   17419    440b HA15_20140317w.cpp.elf
 17346      790     426   18562    4882 HA15_20140317w.cpp.elf

Baris pertama (dengan teks 17554) tidak berfungsi, setelah banyak pengeditan, baris kedua (dengan teks 16316) berfungsi sebagaimana mestinya.

sunting: baris ketiga memiliki semuanya berfungsi, membaca serial, fungsi baru saya, dll. Saya pada dasarnya menghapus beberapa variabel global dan kode duplikat. Saya menyebutkan ini karena (seperti yang diduga) ini bukan tentang kode ini per sae, itu harus tentang penggunaan RAM. Yang membawa saya kembali ke pertanyaan awal, "bagaimana cara mengukurnya" Saya masih memeriksa beberapa jawaban, terima kasih.

Bagaimana cara saya menafsirkan informasi di atas?

Sejauh ini pemahaman saya adalah:

`TEXT` is program instruction memory
`DATA` is variables (unitialised?) in program memory
`BSS`  is variables occupying RAM

karena BSS jauh lebih kecil dari 1024 byte, mengapa yang kedua bekerja, tetapi yang pertama tidak? Jika itu DATA+BSSmaka keduanya menempati lebih dari 1024.

sunting kembali: Saya mengedit pertanyaan untuk memasukkan kode, tetapi sekarang saya telah menghapusnya karena itu benar-benar tidak ada hubungannya dengan masalah (selain praktik pengkodean yang mungkin buruk, deklarasi variabel dan sejenisnya). Anda dapat meninjau kode dengan melihat kembali melalui pengeditan jika Anda benar - benar ingin melihatnya. Saya ingin kembali ke pertanyaan yang ada, yang lebih didasarkan pada: Bagaimana mengukur penggunaan RAM.

Madivad
sumber
Saya pikir saya akan menambahkan, saya telah menambahkan berbagai bagian kode baru selama beberapa minggu terakhir, kemudian memilihnya sampai berfungsi, tapi sekarang saya hanya menambahkan setengah byte byte dan saya sudah selesai ... :(
Madivad
Apakah Anda menggunakan Stringketik di program Anda? Hal ini diketahui sering melakukan alokasi dan pelepasan memori dinamis, yang dapat memecah tumpukan hingga ke titik di mana Anda mungkin tidak memiliki memo lagi.
jfpoilpret
@ jfpoilpret Saya tinggal jauh dari Strings karena overhead. Saya senang bekerja dengan array char, yang mengatakan, saya hampir selalu mendefinisikan semua array char saya dengan ukuran tetap (saat ini, saya memiliki satu byte array yang tidak murni karena saya mengubah panjang konten untuk kompilasi ulang yang berbeda.
Madivad
Posting kode Anda di sini (atau untuk pastebin jika terlalu besar) mungkin hekp mencari tahu masalah apa yang Anda temui dengan memori.
jfpoilpret
@ jfpoilpret Saya tidak bisa benar-benar memposting kode, itu besar dan sayangnya sangat kembung, tersebar di 16 file. Itu adalah proyek yang saya ijinkan untuk tumbuh jauh melampaui apa yang diminta (beberapa proyek digabung bersama). Saya mulai sekarang untuk memecahnya yang saya yakin akan membantu untuk memperbaiki masalah. Meskipun ada beberapa bagian yang saya perlu dilihat orang (atau membimbing saya), saya akan mempostingnya nanti.
Madivad

Jawaban:

15

Anda dapat menggunakan fungsi yang disediakan AVRGCC: Memonitor Penggunaan Stack

Fungsi ini dimaksudkan untuk memeriksa penggunaan tumpukan tetapi yang dilaporkan adalah RAM aktual yang belum pernah digunakan (selama eksekusi). Ia melakukannya dengan "mengecat" (mengisi) RAM dengan nilai yang diketahui (0xC5), dan kemudian memeriksa area RAM dengan menghitung berapa byte yang masih memiliki nilai awal yang sama.
Laporan tersebut akan menampilkan RAM yang belum pernah digunakan (RAM gratis minimum) dan karenanya Anda dapat menghitung RAM maks yang telah digunakan (Total RAM - RAM yang dilaporkan).

Ada dua fungsi:

  • StackPaint dijalankan secara otomatis selama inisialisasi dan "mengecat" RAM dengan nilai 0xC5 (dapat diubah jika diperlukan).

  • StackCount dapat dipanggil kapan saja untuk menghitung RAM yang belum digunakan.

Berikut ini adalah contoh penggunaannya. Tidak berbuat banyak tetapi dimaksudkan untuk menunjukkan cara menggunakan fungsi.

// -----------------------------------------------------------------------------
extern uint8_t _end;
extern uint8_t __stack;

void StackPaint(void) __attribute__ ((naked)) __attribute__ ((section (".init1")));

void StackPaint(void)
{
#if 0
    uint8_t *p = &_end;

    while(p <= &__stack)
    {
        *p = 0xc5;
        p++;
    }
#else
    __asm volatile ("    ldi r30,lo8(_end)\n"
                    "    ldi r31,hi8(_end)\n"
                    "    ldi r24,lo8(0xc5)\n" /* STACK_CANARY = 0xc5 */
                    "    ldi r25,hi8(__stack)\n"
                    "    rjmp .cmp\n"
                    ".loop:\n"
                    "    st Z+,r24\n"
                    ".cmp:\n"
                    "    cpi r30,lo8(__stack)\n"
                    "    cpc r31,r25\n"
                    "    brlo .loop\n"
                    "    breq .loop"::);
#endif
} 


uint16_t StackCount(void)
{
    const uint8_t *p = &_end;
    uint16_t       c = 0;

    while(*p == 0xc5 && p <= &__stack)
    {
        p++;
        c++;
    }

    return c;
} 

// -----------------------------------------------------------------------------

void setup() {

Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
Serial.println(StackCount(), DEC);  // calls StackCount() to report the unused RAM
delay(1000);
}
alexan_e
sumber
sepotong kode yang menarik, terima kasih. Saya memang menggunakannya, dan itu menunjukkan ada 600+ byte yang tersedia, tetapi ketika saya mengubur bahwa dalam subs yang lebih dalam itu berkurang, tetapi tidak hilang. Jadi MUNGKIN masalah saya ada di tempat lain.
Madivad
@Madivad Perhatikan bahwa 600+ byte ini mewakili jumlah RAM minimum yang tersedia hingga Anda memanggil StackCount. Itu tidak benar-benar membuat perbedaan seberapa dalam Anda melakukan panggilan, jika sebagian besar kode dan panggilan bersarang telah dieksekusi sebelum memanggil StackCount maka hasilnya akan benar. Jadi misalnya Anda dapat membiarkan papan Anda berfungsi untuk sementara waktu (selama yang diperlukan untuk mendapatkan cakupan kode yang cukup atau idealnya sampai Anda mendapatkan perilaku yang Anda uraikan) dan kemudian tekan tombol untuk mendapatkan RAM yang dilaporkan. Jika ada cukup maka itu bukan penyebab masalah.
alexan_e
1
Terima kasih @alexan_e, saya telah membuat area pada layar saya yang melaporkan ini sekarang, jadi ketika saya maju dalam beberapa hari ke depan saya akan menonton nomor ini dengan penuh minat, terutama ketika gagal! Terima kasih lagi
Madivad
@Madivad Harap dicatat bahwa fungsi yang diberikan tidak akan melaporkan hasil yang benar jika malloc () digunakan dalam kode
alexan_e
terima kasih untuk itu, saya sadar, telah disebutkan. Sejauh yang saya ketahui, saya tidak menggunakannya (saya tahu bahwa mungkin ada perpustakaan yang menggunakannya, saya belum memeriksa sepenuhnya).
Madivad
10

Masalah utama yang dapat Anda miliki dengan penggunaan memori saat runtime adalah:

  • tidak ada memori yang tersedia di heap untuk alokasi dinamis ( mallocatau new)
  • tidak ada ruang yang tersisa di tumpukan saat memanggil suatu fungsi

Keduanya sebenarnya sama dengan AVR SRAM (2K pada Arduino) digunakan untuk keduanya (selain data statis yang ukurannya tidak pernah berubah selama eksekusi program).

Secara umum, alokasi memori dinamis jarang digunakan pada MCU, hanya beberapa perpustakaan yang biasanya menggunakannya (salah satunya adalah Stringkelas, yang Anda sebutkan tidak digunakan, dan itu bagus).

Tumpukan dan tumpukan dapat dilihat pada gambar di bawah ini (milik Adafruit ): masukkan deskripsi gambar di sini

Oleh karena itu, masalah yang paling diharapkan datang dari stack overflow (yaitu ketika stack tumbuh menuju heap dan overflow di atasnya, dan kemudian -jika heap tidak digunakan sama sekali- meluap di zona data statis SRAM. Pada saat itu, Anda memiliki risiko tinggi:

  • korupsi data (yaitu tumpukan tumpukan data atau data statis), memberi Anda perilaku yang tidak dapat dipahami
  • tumpukan korupsi (yaitu tumpukan atau data statis menimpa konten tumpukan), umumnya mengarah pada kerusakan

Untuk mengetahui jumlah memori yang tersisa antara bagian atas tumpukan dan bagian atas tumpukan (sebenarnya, kami dapat menyebutnya bagian bawah jika kami mewakili tumpukan dan tumpukan pada gambar yang sama seperti yang digambarkan di bawah), Anda dapat menggunakan fungsi berikut:

int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

Dalam kode di atas, __brkvalmenunjuk ke atas tumpukan tetapi 0ketika tumpukan belum digunakan, dalam hal ini kita menggunakan &__heap_startmenunjuk ke mana __heap_start, variabel pertama yang menandai bagian bawah tumpukan; &vmenunjuk ke bagian atas tumpukan (ini adalah variabel terakhir yang didorong pada tumpukan), maka rumus di atas mengembalikan jumlah memori yang tersedia untuk tumpukan (atau tumpukan jika Anda menggunakannya) untuk tumbuh.

Anda dapat menggunakan fungsi ini di berbagai lokasi kode Anda untuk mencoba dan mencari tahu di mana ukuran ini semakin berkurang.

Tentu saja, jika Anda melihat fungsi ini mengembalikan angka negatif maka sudah terlambat: Anda sudah terlalu banyak menumpuk!

jfpoilpret
sumber
1
Untuk moderator: maaf karena menempatkan posting ini ke wiki komunitas, saya pasti membuat kesalahan saat mengetik, di tengah posting. Harap letakkan kembali di sini karena tindakan ini tidak disengaja. Terima kasih.
jfpoilpret
terima kasih atas jawaban ini, saya benar-benar HANYA menemukan sepotong kode itu hampir satu jam yang lalu (di bagian bawah playground.arduino.cc/Code/AvailableMemory#.UycOrueSxfg ). Saya belum memasukkannya (tapi saya akan) karena saya memiliki area yang cukup besar untuk debugging pada layar saya. Saya pikir saya telah bingung tentang menetapkan barang secara dinamis. Apakah mallocdan newsatu-satunya cara saya bisa melakukan itu? Jika demikian, maka saya tidak punya apa-apa yang dinamis. Juga, saya baru tahu bahwa UNO memiliki 2K SRAM. Saya pikir itu 1K. Mempertimbangkan ini, saya tidak kehabisan RAM! Saya perlu mencari di tempat lain.
Madivad
Selain itu, ada juga calloc. Tetapi Anda mungkin menggunakan lib pihak ke-3 yang menggunakan alokasi dinamis tanpa Anda sadari (Anda harus memeriksa kode sumber dari semua dependensi Anda untuk memastikannya)
jfpoilpret
2
Menarik. Satu-satunya "masalah" adalah bahwa ia melaporkan RAM gratis pada titik di mana ia dipanggil, jadi kecuali itu ditempatkan di bagian kanan Anda mungkin tidak melihat tumpukan overrun. Fungsi yang saya sediakan tampaknya memiliki keuntungan di bidang itu karena melaporkan RAM bebas minimal hingga saat itu, setelah alamat RAM digunakan, tidak dilaporkan lagi bebas (di sisi bawah mungkin ada beberapa RAM yang terisi byte yang cocok dengan nilai "paint" dan dilaporkan sebagai gratis). Selain itu mungkin satu cara lebih baik daripada yang lain tergantung pada apa yang diinginkan pengguna.
alexan_e
Poin bagus! Saya tidak memperhatikan poin spesifik ini dalam jawaban Anda (dan bagi saya yang tampak seperti bug pada kenyataannya), sekarang saya melihat titik "mengecat" zona bebas di muka. Mungkin Anda bisa membuat poin ini lebih eksplisit dalam jawaban Anda?
jfpoilpret
7

Ketika Anda mencari cara untuk menemukan file .elf yang dihasilkan di direktori sementara Anda, Anda dapat menjalankan perintah di bawah ini untuk membuang penggunaan SRAM, di mana project.elfharus diganti dengan .elffile yang dihasilkan . Keuntungan dari output ini adalah kemampuan untuk memeriksa bagaimana SRAM Anda digunakan. Apakah semua variabel harus bersifat global, apakah semuanya benar-benar diperlukan?

avr-objdump -S -j .bss project.elf

project.elf:     file format elf32-avr


Disassembly of section .bss:

00800060 <__bss_start>:
        ...

00800070 <measurementReady>:
        ...

00800071 <cycles>:
        ...

00800073 <measurement>:
  800073:       00 00 00 00                                         ....

00800077 <measurementStart>:
  800077:       00 00 00 00                                         ....

0080007b <timerOverflows>:
  80007b:       00 00 00 00

Perhatikan bahwa ini tidak menunjukkan tumpukan atau penggunaan memori dinamis seperti yang dicatat Ignacio Vazquez-Abrams dalam komentar di bawah.

Selain itu a avr-objdump -S -j .data project.elfdapat diperiksa, tetapi tidak ada program saya yang menghasilkan sesuatu dengan itu jadi saya tidak bisa memastikan apakah itu berguna. Ini seharusnya ke daftar 'diinisialisasi (non-nol) Data'.

jippie
sumber
Atau Anda bisa menggunakannya avr-size. Tapi itu tidak akan menunjukkan alokasi dinamis atau menumpuk penggunaan.
Ignacio Vazquez-Abrams
@ IgnacioVazquez-Abrams tentang dinamika, sama untuk solusi saya. Mengedit jawaban saya.
jippie
Ok, ini adalah jawaban yang paling menarik sejauh ini. Saya telah bereksperimen dengan avr-objdumpdan avr-sizedan saya akan segera mengedit posting saya di atas. Terima kasih untuk ini.
Madivad
3

Saya menduga untuk sementara waktu bahwa saya sampai pada akhir RAM yang tersedia. Saya tidak berpikir saya menggunakan terlalu banyak tumpukan (walaupun itu mungkin), apa cara terbaik untuk menentukan berapa banyak RAM yang sebenarnya saya gunakan?

Akan lebih baik menggunakan kombinasi estimasi manual dan dengan menggunakan sizeofoperator. Jika semua deklarasi Anda statis, maka ini akan memberi Anda gambaran yang akurat.

Jika Anda menggunakan alokasi dinamis, maka Anda mungkin mengalami masalah setelah Anda mulai deallocating memori. Ini karena fragmentasi memori pada heap.

Melewati dan mencoba menyelesaikannya, saya memiliki masalah ketika saya mendapatkan enum dan struct; berapa memori yang biayanya?

Enum membutuhkan ruang sebanyak int. Jadi, jika Anda memiliki 10 elemen dalam enumdeklarasi, itu akan menjadi 10*sizeof(int). Juga, setiap variabel yang menggunakan enumerasi hanyalah sebuah int.

Untuk struktur, akan lebih mudah digunakan sizeofuntuk mengetahuinya. Struktur menempati ruang (minimum) sama dengan jumlah anggotanya. Jika kompiler melakukan perataan struktur, maka mungkin lebih, namun ini tidak mungkin dalam kasus avr-gcc.

asheeshr
sumber
Saya melakukan semua tugas statis sejauh yang saya bisa. Saya tidak pernah berpikir sizeofuntuk menggunakan untuk tujuan ini. Saat ini, saya sudah memiliki hampir 400 byte (secara global). Sekarang saya akan memeriksa dan menghitung enum (secara manual) dan struct (yang saya punya beberapa — dan saya akan gunakan sizeof), dan melaporkan kembali.
Madivad
Tidak yakin Anda benar-benar perlu sizeofmengetahui ukuran data statis Anda karena ini akan dicetak oleh avrdude IIRC.
jfpoilpret
@ jfpoilpret Itu tergantung versi, saya kira. Tidak semua versi dan platform menyediakannya. Milik saya (Linux, beberapa versi) tidak menunjukkan penggunaan memori untuk satu, sementara versi Mac melakukannya.
asheeshr
Saya telah mencari keluaran verbose, saya pikir seharusnya ada di sana, bukan
Madivad
@ AsheeshR Saya tidak menyadarinya, milik saya berfungsi dengan baik di Windows.
jfpoilpret
1

Ada sebuah program bernama Arduino Builder yang menyediakan visualisasi rapi jumlah flash, SRAM, dan EEPROM yang digunakan oleh program Anda.

Pembuat Arduino

Builder Arduino membentuk bagian dari solusi IDE CodeBlocks Arduino . Ini dapat digunakan sebagai program mandiri atau melalui IDE CodeBlocks Arduino.

Sayangnya Arduino Builder agak lama tetapi harus bekerja untuk sebagian besar program dan sebagian besar Arduino, seperti Uno.

sa_leinad
sumber