Я использую слишком много оперативной памяти. Как это можно измерить?
Я хотел бы узнать, сколько оперативной памяти я использую в своем проекте, насколько я могу судить, на самом деле нет способа это вычислить (кроме как пройтись и посчитать самому). Я дошел до стадии в довольно большом проекте, когда я определил, что у меня заканчивается оперативная память.
Я определил это, потому что я могу добавить раздел, а затем все начнется где-то еще в моем коде без видимой причины. Если я #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) работает как надо.
редактирование: в третьей строке все работает, последовательное чтение, мои новые функции и т. д. По сути, я удалил некоторые глобальные переменные и дублирующий код. Я упоминаю об этом, потому что (как и предполагалось) речь идет не об этом коде как таковом, а об использовании оперативной памяти. Что возвращает меня к изначальному вопросу: «как лучше всего это измерить». Я все еще проверяю некоторые ответы, спасибо.
Как мне на самом деле интерпретировать приведенную выше информацию?
На данный момент я понимаю так:
`TEXT` is program instruction memory
`DATA` is variables (unitialised?) in program memory
`BSS` is variables occupying RAM
поскольку BSS значительно меньше 1024 байт, почему второй работает, а первый нет? Если это DATA+BSS, то оба занимают больше 1024.
повторное редактирование: я отредактировал вопрос, включив код, но теперь я удалил его, потому что он на самом деле не имел никакого отношения к проблеме (кроме, может быть, плохой практики кодирования, объявления переменных и т. п.). Вы можете просмотреть код, просмотрев правки, если вы действительно хотите его увидеть. Я хотел вернуться к вопросу, который был больше основан на: Как измерить использование оперативной памяти.
@Madivad, 👍20
Обсуждение5 ответов
Лучший ответ:
Вы можете использовать предоставленные функции AVRGCC: Мониторинг использования стека
Функция была предназначена для проверки использования стека, но то, что она сообщает, является фактическим объемом оперативной памяти, который никогда не использовался (во время выполнения). Она делает это, «закрашивая» (заполняя) оперативную память известным значением (0xC5), а затем проверяя область оперативной памяти, подсчитывая, сколько байтов все еще имеют то же начальное значение.
В отчете будет показан объем оперативной памяти, который не использовался (минимальный объем свободной оперативной памяти), и, таким образом, вы сможете рассчитать максимальный объем оперативной памяти, который был использован (общий объем оперативной памяти - указанный объем оперативной памяти).
Есть две функции:
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
@Madivad Обратите внимание, что эти 600+ байт представляют собой минимально доступный объём оперативной памяти на момент вызова StackCount. Не имеет значения, насколько глубоко вы разместили вызов: если большая часть кода и вложенных вызовов была выполнена до вызова StackCount, то результат будет правильным. Например, вы можете оставить плату работать некоторое время (столько, сколько потребуется для достижения достаточного покрытия кода, или, в идеале, до появления описанной вами ошибки), а затем нажать кнопку, чтобы получить отчёт об объёме оперативной памяти. Если объёма достаточно, то проблема не в нём., @alexan_e
Спасибо, @alexan_e, я создал раздел на своём дисплее, где это отображается, так что по мере продвижения в ближайшие дни буду с интересом следить за этим номером, особенно когда он будет падать! Ещё раз спасибо., @Madivad
@Madivad Обратите внимание, что данная функция не выдаст правильные результаты, если в коде используется [malloc()](http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&p=365568&sid=e5ecab822bd4a3fd231659420b4033e0#365568), @alexan_e
Спасибо, я в курсе, об этом упоминалось. Насколько мне известно, я этим не пользуюсь (знаю, что может быть какая-то библиотека, использующая это, но я пока не проверял полностью)., @Madivad
Я подозревал некоторое время, что я приближаюсь к концу доступной оперативной памяти. Я не думаю, что я использую слишком много стека (хотя это возможно), как лучше всего определить, сколько оперативной памяти я фактически использую?
Лучше всего использовать комбинацию ручной оценки и использования оператора sizeof. Если все ваши объявления статические, то это должно дать вам точную картину.
Если вы используете динамическое выделение, то вы можете столкнуться с проблемой, как только начнете освобождать память. Это происходит из-за фрагментации памяти в куче.
Просматривая и пытаясь разобраться, у меня возникают проблемы, когда я дохожу до перечислений и структур; сколько памяти они занимают?
Перечисление занимает столько же места, сколько и int. Так что, если у вас есть набор из 10 элементов в объявлении enum, это будет 10*sizeof(int). Кроме того, каждая переменная, которая использует перечисление, просто int.
Для структур проще всего использовать sizeof, чтобы узнать. Структуры занимают (минимальное) пространство, равное сумме ее членов. Если компилятор выполняет выравнивание структур, то оно может быть больше, однако это маловероятно в случае avr-gcc.
Я назначаю всё статически, насколько это возможно. Никогда не думал использовать sizeof для этой цели. На данный момент у меня уже учтено почти 400 байт (глобально). Сейчас я пройдусь по перечислениям (вручную) и структурам (которых у меня несколько, и я буду использовать sizeof), и отчитаюсь., @Madivad
Не уверен, что вам действительно нужно sizeof, чтобы узнать размер ваших статических данных, так как это выводится avrdude IIRC., @jfpoilpret
@jfpoilpret Думаю, это зависит от версии. Не все версии и платформы поддерживают эту функцию. У меня (Linux, несколько версий) не отображается использование памяти для одной из них, а версии для Mac — показывает., @asheeshr
Я просмотрел подробный вывод, я думал, что он должен быть там, но его там нет., @Madivad
@AsheeshR Я не знал об этом, у меня всё отлично работает на Windows., @jfpoilpret
Когда вы поймете, как найти сгенерированный файл .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. Но это не покажет вам динамическое выделение памяти или использование стека., @Ignacio Vazquez-Abrams
@IgnacioVazquez-Abrams насчёт динамики, то же самое и с моим решением. Отредактировал свой ответ., @jippie
Хорошо, это пока самый интересный ответ. Я экспериментировал с avr-objdump и avr-size и скоро отредактирую свой пост выше. Спасибо., @Madivad
Основные проблемы, которые могут возникнуть при использовании памяти во время выполнения:
- нет доступной памяти в куче для динамического выделения памяти (
mallocилиnew) - не осталось места в стеке при вызове функции
Оба на самом деле одно и то же, поскольку для обоих используется SRAM AVR (2 КБ на Arduino) (в дополнение к статическим данным, размер которых никогда не меняется во время выполнения программы).
Обычно динамическое выделение памяти редко используется в микроконтроллерах, его используют только несколько библиотек (одна из них — класс 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 указывает, конечно же, на вершину стека (это последняя переменная, помещенная в стек), поэтому приведенная выше формула возвращает объем памяти, доступной для роста стека (или кучи, если вы ее используете).
Вы можете использовать эту функцию в различных местах вашего кода, чтобы попытаться выяснить, где этот размер резко уменьшается.
Конечно, если вы когда-нибудь увидите, что эта функция возвращает отрицательное число, то будет слишком поздно: вы уже переполнили стек!
Модераторам: извините за размещение этого сообщения в вики сообщества. Должно быть, я допустил ошибку при наборе текста, где -то в середине сообщения. Пожалуйста, верните его сюда, так как это было сделано непреднамеренно. Спасибо., @jfpoilpret
Спасибо за ответ, я буквально только что нашёл этот фрагмент кода (внизу http://playground.arduino.cc/Code/AvailableMemory#.UycOrueSxfg ). Я пока не включил его (но обязательно включу), так как у меня на дисплее довольно большая область для отладки. Кажется, я запутался с динамическим назначением данных. Неужели malloc и new — единственный способ это сделать? Если так, то у меня нет никакой динамической памяти. Кроме того, я только что узнал, что у UNO 2 КБ SRAM. Я думал, что там 1 КБ. Учитывая это, у меня не заканчивается оперативная память! Мне нужно поискать что-нибудь другое., @Madivad
Кроме того, есть ещё calloc. Но вы можете использовать сторонние библиотеки, которые используют динамическое выделение памяти, без вашего ведома (вам придётся проверить исходный код всех ваших зависимостей, чтобы убедиться в этом)., @jfpoilpret
Интересно. Единственная «проблема» заключается в том, что функция сообщает о свободном объёме оперативной памяти в момент вызова, поэтому, если её не разместить в нужной части, переполнение стека может быть не замечено. Предоставленная мной функция, похоже, имеет преимущество в этом отношении, поскольку она сообщает минимальный объём свободной оперативной памяти на данный момент. После использования адреса ОЗУ он больше не сообщается как свободный (недостаток же заключается в том, что некоторые занятые байты ОЗУ могут соответствовать значению «paint» и сообщаться как свободные). Кроме того, один из способов может быть более подходящим, в зависимости от потребностей пользователя., @alexan_e
Хорошее замечание! Я не заметил этого в вашем ответе (и мне это показалось ошибкой), теперь я понимаю, почему нужно было «рисовать» свободную зону заранее. Может быть, вы могли бы более чётко обозначить это в своём ответе?, @jfpoilpret
Интересно, будут ли функции Serial.print() работать после вашей функции freeRam(), потому что, возможно, *ещё* будет слишком поздно. Возможно, стоит сделать так, чтобы обработчик прерывания вызывал freeRam() и, если результат отрицательный, выводил сообщение в монитор последовательного порта. Не знаю, можно ли также выводить данные о размерах кучи или стека., @Kelly S. French
Не рекомендуется использовать Serial из обработчика прерываний (ISR). Проблема, когда freeRam() возвращает отрицательное значение, заключается в том, что стек уже переполнен (или наоборот), что означает неожиданное изменение некоторых данных. Если куча переполняет стек, это, скорее всего, приведёт к неизбежному сбою. В противном случае сбоя может и не произойти, но некоторые потенциально важные данные будут уничтожены., @jfpoilpret
Существует программа под названием Arduino Builder, которая обеспечивает наглядную визуализацию объема флэш-памяти, SRAM и EEPROM, используемого вашей программой.
Arduino builder является частью решения CodeBlocks Arduino IDE. Его можно использовать как отдельную программу или через CodeBlocks Arduino IDE.
К сожалению, Arduino Builder немного устарел, но он должен работать для большинства программ и большинства Arduino, таких как Uno.
- Является ли использование malloc() и free() действительно плохой идеей для Arduino?
- Есть ли способ подключить оперативную память компьютера к Arduino?
- Последовательная печать из флэш-памяти (F() macro, PROGMEM, sprintf_P, SPTR)
- Есть ли способ добавить внешнюю оперативную память (скажем, 100 МБ или 200 МБ) в этом контексте микширования аудиобуфера?
- Проблема с массивом + последовательным монитором
- Локально объявленная переменная занимает глобальное переменное пространство в динамической памяти/SRAM
- Использование внутренней памяти ESP32 Cam
- Очищается ли SRAM при переводе Arduino в режим sleep_mode_pwr_down?
Я подумал, что стоит добавить: за последние несколько недель я добавил несколько новых разделов кода, затем оптимизировал его до тех пор, пока он не заработал, но теперь я добавил только полдюжины байтовых переменных, и все... :(, @Madivad
Используете ли вы тип
Stringв своих программах? Известно, что он часто выполняет динамическое выделение и освобождение памяти, что может фрагментировать кучу до такой степени, что у вас может не остаться памяти., @jfpoilpret@jfpoilpret Я избегаю
Stringиз-за накладных расходов. Мне нравится работать с массивами символов, но я почти всегда определяю все свои массивы символов с фиксированным размером (на данный момент у меня есть ОДИН байтовый массив, который не является чисто потому, что я меняю длину содержимого для разных перекомпиляций)., @MadivadРазмещение вашего кода здесь (или на pastebin, если он слишком большой) может помочь выяснить, с какими проблемами с памятью вы столкнулись., @jfpoilpret
@jfpoilpret Я не могу выложить код, он огромный и, к сожалению, очень раздутый, разбросан по 16 файлам. Это был проект, которому я позволил разрастись намного больше, чем требовалось (это несколько проектов, объединенных вместе). Сейчас я начинаю разбивать его на части, что, я уверен, поможет решить проблему. Хотя есть некоторые части, на которые мне нужно, чтобы люди посмотрели (или дали мне указания), я выложу их позже., @Madivad
может быть, вы могли бы просто разместить раздел, который вы удалили с
#ifndef, это могло бы дать нам подсказки; еще один момент: вы не упомянули, какой Arduino вы используете. Список используемых вами библиотек также может быть полезен. Наконец, вывод, выдаваемый avrdude, как правило (работает на Windows), включает размер статических данных, что может очень помочь., @jfpoilpretХотя ваш вопрос, похоже, связан с потреблением памяти во время выполнения, вы хотели бы рассмотреть возможность использования серии 1.5.x Arduino IDE: она сообщает объем «статической» оперативной памяти, используемой скетчем (объявления переменных), сразу после компиляции. Это не будет точным, если ваш код использует malloc, но это начало., @Federico Fissore
Да, хорошо, я скоро отредактирую, чтобы включить немного кода. Я сделал так много изменений и сокращений с прошлой ночи, удаляя и настраивая вещи и просматривая выходные данные
avr-sizeиavr-objdump. Я выложу код, который сломал верблюдов, @MadivadЯ просто подумал, что стоит добавить, я не думаю, что в коде выше есть что-то изначально неправильное, на самом деле, если я уберу другие сегменты кода, то код выше будет работать так, как и ожидалось. На самом деле, сам этот код ВСЕГДА работает так, как и ожидалось. Он ЛОМАЕТСЯ в другом месте кода (по сути, он нарушает прием последовательной связи)., @Madivad
Что касается вашего замечания о
bssиdata:data— это инициализированные глобальные данные, которые изначально находятся во Flash и копируются в SRAM при запуске программы;bss— это неинициализированные глобальные данные, которые существуют только в SRAM. Следовательно, оба используют SRAM в целом. То, что остается в SRAM, предназначено для стека и кучи., @jfpoilpretОзначает ли это, что используемая SRAM, находящаяся в эксплуатации, будет эквивалентна как минимум .bss PLUS .data? Или есть некоторое совпадение?, @Madivad
Это ПЛЮС, нет совпадений (к счастью, я бы добавил!), @jfpoilpret