Я использую слишком много оперативной памяти. Как это можно измерить?

Я хотел бы узнать, сколько оперативной памяти я использую в своем проекте, насколько я могу судить, на самом деле нет способа это вычислить (кроме как пройтись и посчитать самому). Я дошел до стадии в довольно большом проекте, когда я определил, что у меня заканчивается оперативная память.

Я определил это, потому что я могу добавить раздел, а затем все начнется где-то еще в моем коде без видимой причины. Если я #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.

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

, 👍20

Обсуждение

Я подумал, что стоит добавить: за последние несколько недель я добавил несколько новых разделов кода, затем оптимизировал его до тех пор, пока он не заработал, но теперь я добавил только полдюжины байтовых переменных, и все... :(, @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


5 ответов


Лучший ответ:

15

Вы можете использовать предоставленные функции 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


3

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

Лучше всего использовать комбинацию ручной оценки и использования оператора 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


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. Но это не покажет вам динамическое выделение памяти или использование стека., @Ignacio Vazquez-Abrams

@IgnacioVazquez-Abrams насчёт динамики, то же самое и с моим решением. Отредактировал свой ответ., @jippie

Хорошо, это пока самый интересный ответ. Я экспериментировал с avr-objdump и avr-size и скоро отредактирую свой пост выше. Спасибо., @Madivad


10

Основные проблемы, которые могут возникнуть при использовании памяти во время выполнения:

  • нет доступной памяти в куче для динамического выделения памяти (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


1

Существует программа под названием Arduino Builder, которая обеспечивает наглядную визуализацию объема флэш-памяти, SRAM и EEPROM, используемого вашей программой.

Arduino Builder

Arduino builder является частью решения CodeBlocks Arduino IDE. Его можно использовать как отдельную программу или через CodeBlocks Arduino IDE.

К сожалению, Arduino Builder немного устарел, но он должен работать для большинства программ и большинства Arduino, таких как Uno.

,