Я використовую занадто багато оперативної пам'яті. Як це можна виміряти?


19

Мені хотілося б знати, скільки оперативної пам’яті я використовую в своєму проекті, наскільки я можу сказати, немає ніякого способу реально розробити це (крім того, щоб пройти і обчислити її самостійно). Я потрапив на етап досить великого проекту, де я визначив, що мені не вистачає оперативної пам’яті.

Я визначив це, тому що я можу додати розділ, і тоді все пекло проривається десь у моєму коді без видимих ​​причин. Якщо я #ifndefщось інше вийду, це знову працює. З новим кодом програмно нічого поганого немає.

Я деякий час підозрював, що я добираюся до кінця наявної оперативної пам’яті. Я не думаю, що я використовую занадто багато стека (хоча це можливо), який найкращий спосіб визначити, скільки оперативної пам’яті я використовую?

Проходячи і намагаючись розробити це, у мене виникають проблеми, коли я потрапляю до перерахунків і структур; скільки пам'яті вони коштують?

Перша редакція: ТАКОЖ, я так редагував свій ескіз з початку, це не фактичні результати, які я спочатку отримав, але це те, що я отримую зараз.

  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

Перший рядок (з текстом 17554) не працював, після довгого редагування другий рядок (з текстом 16316) працює як слід.

редагувати: у третьому рядку є все, що працює, послідовне читання, мої нові функції тощо. Я по суті видалив деякі глобальні змінні та повторюваний код. Я згадую про це, оскільки (як підозрюється) мова йде не про цей код на sae, а про використання оперативної пам'яті. Що повертає мене до початкового питання "як найкраще виміряти", я все ще перевіряю деякі відповіді, дякую.

Як я насправді інтерпретую вищевказану інформацію?

Поки що моє розуміння:

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

оскільки BSS значно менше 1024 байт, чому другий працює, а перший не? Якщо це DATA+BSSтоді обидва займають більше 1024.

повторне редагування: я відредагував питання, щоб включити код, але тепер його видалив, оскільки він справді не мав нічого спільного з проблемою (крім, можливо, поганих методів кодування, оголошень змінних тощо). Ви можете переглянути код, оглянувши правки, якщо дійсно хочете його побачити. Я хотів повернутися до питання, яке базувалося на: Як виміряти використання оперативної пам'яті.


Я думав, що додам, я додав різні нові розділи коду за останні кілька тижнів, потім оптимізував його, поки він не працює, але тепер я додав лише половину дози байтів і закінчую ... :(
Madivad

Чи використовуєте ви Stringтип у своїх програмах? Це, як відомо, здійснює часті динамічні розподіли пам'яті та випуски, які можуть фрагментувати купу до тієї точки, де у вас може не залишитися жодної пам’яті.
jfpoilpret

@jfpoilpret Я тримаюсь подалі від Strings із-за накладних витрат. Я задоволений роботою з масивами char, але сказав, що майже завжди визначаю всі свої масиви char із фіксованим розміром (на даний момент у мене є ОДИН байтовий масив, який не є суто тим, що я змінюю довжину вмісту для різних перекомпіляцій.
Мадівад

Якщо розмістити свій код тут (або на пастібі, якщо він занадто великий), ви зможете дізнатися, з якими проблемами ви стикаєтесь з пам'яттю.
jfpoilpret

@jfpoilpret Я не можу дійсно розмістити код, він величезний і, на жаль, дуже роздутий, поширюється на 16 файлів. Це був проект, який я дозволяв рости набагато більше, ніж потрібно (це кілька проектів, об'єднаних разом). Зараз я починаю розбивати його, що, напевне, допоможе вирішити проблему. Хоча є деякі його частини, які мені потрібні, щоб люди подивилися (або направляли мене далі), я опублікую їх пізніше.
Мадівад

Відповіді:


15

Ви можете використовувати функції, надані AVRGCC: Моніторинг використання стека

Функція призначена для перевірки використання стека, але те, про що він повідомляє, - це фактична ОЗУ, яка ніколи не використовувалася (під час виконання). Це робиться шляхом "фарбування" (заповнення) ОЗУ з відомим значенням (0xC5), а потім перевірки області ОЗУ підрахунку кількості байтів, які мають ще однакове початкове значення.
У звіті буде показано оперативну пам’ять, яка не була використана (мінімум вільної оперативної пам’яті), і для цього ви зможете розрахувати максимально використану оперативну пам’ять (Total RAM - повідомлена оперативна пам’ять).

Є дві функції:

  • StackPaint виконується автоматично під час ініціалізації та "фарбує" ОЗУ зі значенням 0xC5 (може бути змінено за потреби).

  • StackCount можна викликати в будь-якій точці для підрахунку оперативної пам'яті, яка не використовується.

Ось приклад використання. Не робить багато, але призначений показати, як використовувати функції.

// -----------------------------------------------------------------------------
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);
}

цікавий фрагмент коду, дякую. Я використовував його, і це дозволяє припустити, що є 600+ байтів, але коли я закопую це в глибшій підводці, воно зменшується, але не стирається. Тож МОЖИТЕ моя проблема в іншому місці.
Мадівад

@Madivad Зауважте, що цей 600+ байт представляє мінімально доступну кількість оперативної пам’яті аж до моменту, коли ви викликали StackCount. Це насправді не має значення, наскільки глибоко ви здійснюєте виклик, якщо більшість коду та вкладених викликів були виконані перед викликом StackCount, тоді результат буде правильним. Так, наприклад, ви можете залишити свою плату деякий час (стільки, скільки потрібно, щоб отримати достатнє покриття коду, або в ідеалі, поки ви не отримаєте описане недобросовісне поведінку), а потім натиснути кнопку, щоб отримати повідомлену оперативну пам'ять. Якщо їх достатньо, то це не є причиною проблем.
alexan_e

1
Дякую @alexan_e, я створив на моєму дисплеї область, яка повідомляє про це зараз, тому, коли я прогресую протягом наступних кількох днів, я з цікавістю дивлюсь цей номер, особливо коли він не працює! Ще раз
дякую

@Madivad Зверніть увагу, що дана функція не повідомить про правильні результати, якщо malloc () буде використаний у коді
alexan_e

дякую за це, я знаю, це було згадано. Наскільки мені відомо, я не використовую його (я знаю, що може бути бібліотека, яка використовує її, я ще не перевірила повністю).
Мадівад

10

Основні проблеми, які можуть виникнути при використанні пам'яті під час виконання:

  • відсутність пам’яті в купі для динамічних розподілів ( mallocабо new)
  • під час виклику функції не залишається місця на стеку

Обидва фактично такі ж, як AVR SRAM (2K на Arduino) використовується для обох (крім статичних даних, розмір яких ніколи не змінюється під час виконання програми).

Взагалі, динамічний розподіл пам’яті рідко використовується на MCU, лише кілька бібліотек, як правило, використовують його (одна з них - Stringклас, який ви згадали, що ви не використовуєте, і це хороший момент).

Стек і купу можна побачити на малюнку нижче (люб’язно надано Adafruit ): введіть тут опис зображення

Отже, найбільш очікувана проблема виникає через переповнення стека (тобто коли стек росте у напрямку до купи та переливається на нього, а потім - якщо купа взагалі не використовувалася - переповнення в зоні статичних даних SRAM. У той час, у вас високий ризик:

  • пошкодження даних (тобто стек перезаписує купу або статичні дані), що дає вам незрозумілу поведінку
  • пошкодження стека (тобто купа або статичні дані перезаписують вміст стека), що, як правило, призводить до збоїв

Щоб дізнатись об’єм пам’яті, що залишився між вершиною купи та вершиною стека (насправді, ми можемо назвати це нижньою частиною, якщо ми представлятимемо як купу, так і стек на тому самому зображенні, як зображено нижче), ви може використовувати таку функцію:

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

У наведеному вище коді __brkvalвказує на верхню частину купи, але 0коли купа не була використана, і в цьому випадку ми використовуємо, на &__heap_startякі вказує __heap_start, перша змінна, що позначає дно купи; &vточки, звичайно, у верхній частині стека (це остання змінна, натиснута на стек), отже, вищевказана формула повертає об'єм пам'яті, доступний для стека (або купи, якщо ви його використовуєте) для зростання.

Ви можете використовувати цю функцію в різних місцях коду, щоб спробувати дізнатися, де цей розмір різко зменшується.

Звичайно, якщо ви коли-небудь побачите, що ця функція повертає від'ємне число, тоді вже пізно: ви вже перекинули стек!


1
Модераторам: вибачте за те, що виклали цю публікацію у вікі спільноти, я, мабуть, зробив щось не так під час набору тексту, посеред публікації. Будь ласка, поверніть його сюди, оскільки ця дія була ненавмисною. Спасибі.
jfpoilpret

спасибі за цю відповідь, я буквально лише ДУСТО знайшов цей фрагмент коду лише годину тому (внизу playground.arduino.cc/Code/AvailableMemory#.UycOrueSxfg ). Я ще не включив його (але буду), оскільки у мене на екрані є досить велика область для налагодження. Я думаю, що мене розгубило динамічне призначення матеріалів. Є mallocі newєдиний спосіб , яким я можу це зробити? Якщо так, то у мене нічого динамічного. Крім того, я щойно дізнався, що ООН має 2K SRAM. Я подумав, що це 1К. Враховуючи це, у мене не закінчується оперативна пам’ять! Мені потрібно шукати в іншому місці.
Мадівад

Крім того, є також calloc. Але ви можете використовувати сторонні лібри, які використовують динамічний розподіл, не знаючи про це (вам доведеться перевірити вихідний код усіх ваших залежностей, щоб бути впевненим у цьому)
jfpoilpret

2
Цікаво. Єдина «проблема» полягає в тому, що він повідомляє про безкоштовну оперативну пам’ять у точці, куди вона викликається, тому, якщо вона не розміщена у правій частині, ви можете не помітити перевищення стека. Функція, яку я надав, схоже, має перевагу в цій області, оскільки вона повідомляє про мінімальну вільну оперативну пам’ять до цього моменту, після використання адреси ОЗУ більше не повідомляється про вільну (на нижній стороні може бути деяка зайнята ОЗУ байтів, що відповідають значенню "фарба" і повідомляються як вільні). Крім цього, можливо, один спосіб підходить краще, ніж інший залежно від того, що хоче користувач.
alexan_e

Гарна думка! Я не помітив цього конкретного моменту у вашій відповіді (а для мене це виглядало як помилка), тепер я бачу сенс "фарбувати" вільну зону наперед. Можливо, ви могли б зробити це більш чітким у своїй відповіді?
jfpoilpret

7

Коли ви з'ясуєте, як знайти згенерований .elf файл у тимчасовому каталозі, ви можете виконати команду нижче, щоб скинути використання SRAM, де project.elfйого слід замінити на створений .elfфайл. Перевагою цього виходу є можливість перевірити, як використовується ваш SRAM. Чи всі змінні повинні бути глобальними, чи справді всі вони потрібні?

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

Зауважте, що це не показує стек або динамічне використання пам'яті, як зазначив Ігнасіо Васкес-Абрамс у коментарях нижче.

Додатково avr-objdump -S -j .data project.elfможна перевірити, але жодна з моїх програм нічого з цього не видає, тому я не можу точно сказати, чи корисна вона. Він повинен перелічити "ініціалізовані (ненульові) дані".


Або ви могли просто скористатися avr-size. Але це не покаже вам динамічні асигнування або використання стека.
Ігнасіо Васкес-Абрамс

@ IgnacioVazquez-Abrams про динаміку, однакову для мого рішення. Відредагував мою відповідь.
джиппі

Гаразд, це найцікавіша відповідь поки що. Я експериментував avr-objdumpі avr-sizeнезабаром відредагую свою публікацію. Дякую за це
Мадівад

3

Я деякий час підозрював, що я добираюся до кінця наявної оперативної пам’яті. Я не думаю, що я використовую занадто багато стека (хоча це можливо), який найкращий спосіб визначити, скільки оперативної пам’яті я використовую?

Найкраще було б використовувати комбінацію ручного оцінювання та використання sizeofоператора. Якщо всі ваші декларації статичні, то це має дати точну картину.

Якщо ви використовуєте динамічні асигнування, ви можете зіткнутися з проблемою, як тільки почнете розбирати пам'ять. Це пов’язано з фрагментацією пам’яті на купі.

Проходячи і намагаючись розробити це, у мене виникають проблеми, коли я потрапляю до перерахунків і структур; скільки пам'яті вони коштують?

Перерахунок займає стільки ж місця, як і int. Отже, якщо у вас є набір з 10 елементів у enumдекларації, це було б 10*sizeof(int). Крім того, кожна змінна, яка використовує перерахування, просто є int.

Для структур це було б найпростіше використати sizeofдля з'ясування. Структури займають (мінімальний) простір, рівний сумі його членів. Якщо компілятор виконує вирівнювання структури, то їх може бути більше, однак це малоймовірно у випадку avr-gcc.


Я статично призначаю все, наскільки можу. Я ніколи не думав використовувати sizeofдля цієї мети. На даний момент у мене вже майже 400 байт (уже в усьому світі). Зараз я перегляну і обчисляю перерахунки (вручну) та структури (яких у мене є декілька - і я використаю sizeof), і звітую про це.
Мадівад

Не впевнені, що вам дійсно потрібно sizeofзнати розмір своїх статичних даних, оскільки вони надрукуються avrdude IIRC.
jfpoilpret

@jfpoilpret Я думаю, що це залежить від версії. Не всі версії та платформи цього передбачають. Шахта (Linux, кілька версій) не показує використання пам'яті для однієї, в той час як версії Mac.
asheeshr

Я шукав багатослівний вихід, я думав, що він повинен бути там, це не так
Мадівад

@AsheeshR Я не знав про це, мій працює добре в Windows.
jfpoilpret

1

Існує програма під назвою Arduino Builder, яка забезпечує чітку візуалізацію кількості спалаху, SRAM та EEPROM, яку використовує ваша програма.

Ардуїно будівельник

Конструктор Arduino складає частину рішення IDE CodeBlocks Arduino . Він може використовуватися як окрема програма, так і через IDB CodeBlocks Arduino.

На жаль, Arduino Builder трохи старий, але він повинен працювати для більшості програм і більшості Arduinos, таких як Uno.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.