Я использую слишком много оперативной памяти. Как это можно измерить?
Я хотел бы узнать, сколько оперативной памяти я использую в своем проекте, насколько я могу судить, на самом деле нет способа это вычислить (кроме как пройтись и посчитать самому). Я дошел до стадии в довольно большом проекте, когда я определил, что у меня заканчивается оперативная память.
Я определил это, потому что я могу добавить раздел, а затем все начнется где-то еще в моем коде без видимой причины. Если я #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);
}
Я подозревал некоторое время, что я приближаюсь к концу доступной оперативной памяти. Я не думаю, что я использую слишком много стека (хотя это возможно), как лучше всего определить, сколько оперативной памяти я фактически использую?
Лучше всего использовать комбинацию ручной оценки и использования оператора sizeof
. Если все ваши объявления статические, то это должно дать вам точную картину.
Если вы используете динамическое выделение, то вы можете столкнуться с проблемой, как только начнете освобождать память. Это происходит из-за фрагментации памяти в куче.
Просматривая и пытаясь разобраться, у меня возникают проблемы, когда я дохожу до перечислений и структур; сколько памяти они занимают?
Перечисление занимает столько же места, сколько и int
. Так что, если у вас есть набор из 10 элементов в объявлении enum
, это будет 10*sizeof(int)
. Кроме того, каждая переменная, которая использует перечисление, просто int
.
Для структур проще всего использовать sizeof
, чтобы узнать. Структуры занимают (минимальное) пространство, равное сумме ее членов. Если компилятор выполняет выравнивание структур, то оно может быть больше, однако это маловероятно в случае avr-gcc
.
Когда вы поймете, как найти сгенерированный файл .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
, но ни одна из моих программ ничего не выводит с этим, поэтому я не могу сказать наверняка, полезно ли это. Оно должно выводить «инициализированные (ненулевые) данные».
Основные проблемы, которые могут возникнуть при использовании памяти во время выполнения:
- нет доступной памяти в куче для динамического выделения памяти (
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
указывает, конечно же, на вершину стека (это последняя переменная, помещенная в стек), поэтому приведенная выше формула возвращает объем памяти, доступной для роста стека (или кучи, если вы ее используете).
Вы можете использовать эту функцию в различных местах вашего кода, чтобы попытаться выяснить, где этот размер резко уменьшается.
Конечно, если вы когда-нибудь увидите, что эта функция возвращает отрицательное число, то будет слишком поздно: вы уже переполнили стек!
Существует программа под названием 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