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

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) работает как надо.

редактировать: в третьей строке все работает, последовательное чтение, мои новые функции и т. д. Я по существу удалил некоторые глобальные переменные и дублировал код. Я упоминаю об этом, потому что (как и предполагалось) речь идет не об этом коде, а об использовании оперативной памяти. Что возвращает меня к первоначальному вопросу: «Как лучше измерить это?» Я все еще проверяю некоторые ответы, спасибо.

Как я на самом деле интерпретировать вышеупомянутую информацию?

Пока что мое понимание таково:

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

поскольку BSS значительно меньше 1024 байта, почему второй работает, а первый нет? Если это так, DATA+BSSто оба занимают более 1024.

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

Madivad
источник
Я думал, что добавлю, я добавил несколько новых разделов кода за последние несколько недель, затем откомментировал его, пока он не заработал, но теперь я добавил только полдозайта байтовых переменных, и все готово ... :(
Madivad
Вы используете Stringтип в своих программах? Известно, что это часто выполняет динамическое выделение и освобождение памяти, что может привести к фрагментации кучи до такой степени, что у вас может не остаться памяти.
jfpoilpret
@jfpoilpret Я держусь подальше от Strings из-за накладных расходов. Я счастлив работать с массивами символов, тем не менее, я почти всегда определяю все свои массивы символов с фиксированным размером (на данный момент у меня есть ОДИН байтовый массив, который не только потому, что я изменяю длину содержимого для разных перекомпиляций.
Мадивад
Размещение вашего кода здесь (или в pastebin, если он слишком большой) может помочь вам узнать, с какими проблемами вы сталкиваетесь с памятью.
jfpoilpret
@jfpoilpret Я не могу опубликовать код, он огромный и, к сожалению, очень раздутый, состоит из 16 файлов. Это был проект, который я позволял расти намного дальше того, что требовалось (это несколько проектов, объединенных вместе). Сейчас я начинаю разбивать его на части, что, я уверен, поможет решить проблему. Хотя есть некоторые его части, на которые мне нужно посмотреть (или наставить), я опубликую их позже.
Мадивад

Ответы:

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);
}
alexan_e
источник
интересный кусок кода, спасибо. Я использовал его, и он предполагает, что доступно более 600 байт, но когда я закапываю это в более глубоких сабвуферах, оно уменьшается, но не стирается. Так что МОЖЕТ моя проблема в другом месте.
Мадивад
@Madivad Обратите внимание, что эти 600+ байтов представляют минимальный доступный объем оперативной памяти до того момента, когда вы вызвали StackCount. На самом деле не имеет значения, насколько глубоко вы разместите вызов, если большая часть кода и вложенных вызовов была выполнена до вызова StackCount, тогда результат будет правильным. Так, например, вы можете оставить свою плату на некоторое время работающей (до тех пор, пока она не получит достаточного покрытия кода или, в идеале, до тех пор, пока вы не получите описанное вами неправильное поведение), а затем нажмите кнопку, чтобы получить указанную оперативную память. Если этого достаточно, то это не является причиной проблемы.
alexan_e
1
Спасибо @alexan_e, я создал область на моем дисплее, которая сообщает об этом сейчас, так что по мере продвижения в течение следующих нескольких дней я буду с интересом наблюдать это число, особенно когда оно выходит из строя!
Еще
@Madivad Обратите внимание, что данная функция не будет сообщать правильные результаты, если в коде используется malloc ()
alexan_e
спасибо за это, я знаю, это было упомянуто. Насколько я знаю, я не использую его (я знаю, что может быть библиотека, использующая его, я еще не проверил полностью).
Мадивад
10

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

  • нет доступной памяти в куче для динамического выделения ( mallocили new)
  • при вызове функции в стеке не осталось места

Обе они на самом деле одинаковы, поскольку AVR SRAM (2K на 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Конечно, указывает на вершину стека (это последняя переменная, помещаемая в стек), поэтому формула, приведенная выше, возвращает объем памяти, доступный для стека (или кучи, если вы ее используете) для роста.

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

Конечно, если вы когда-нибудь увидите, что эта функция возвращает отрицательное число, тогда уже слишком поздно: вы уже переполнили стек!

мин
источник
1
Модераторам: извините за публикацию этого поста в вики сообщества, я, должно быть, сделал что-то не так при наборе текста в середине поста. Пожалуйста, положите его сюда, так как это действие было непреднамеренным. Благодарю.
jfpoilpret
спасибо за этот ответ, я буквально только что нашел этот кусок кода всего час назад (внизу детской площадки.arduino.cc/ Code/AvailableMemory# .UycOrueSxfg ). Я еще не включил это (но я буду), так как у меня есть довольно большая область для отладки на моем дисплее. Я думаю, что я запутался в динамическом назначении вещей. Есть mallocи newединственный способ , которым я могу это сделать? Если так, то у меня нет ничего динамичного. Кроме того, я только что узнал, что в ООН 2K SRAM. Я думал, что это был 1K. Учитывая это, у меня не хватает оперативной памяти! Мне нужно искать в другом месте.
Мадивад
Кроме того, есть также calloc. Но вы можете использовать сторонние библиотеки, которые используют динамическое размещение, не зная об этом (вам необходимо проверить исходный код всех ваших зависимостей, чтобы быть уверенным в этом)
jfpoilpret
2
Интересный. Единственная «проблема» заключается в том, что она сообщает о свободной оперативной памяти в том месте, где она вызывается, поэтому, если она не помещена в правую часть, вы можете не заметить переполнение стека. Предоставленная мною функция, по-видимому, имеет преимущество в этой области, поскольку она сообщает о минимальной свободной оперативной памяти до этого момента, после использования адреса ОЗУ она больше не сообщается свободной (с другой стороны, может быть некоторая занятая оперативная память). байты, которые соответствуют значению "paint" и сообщаются как свободные). Помимо этого, может быть, один способ подходит лучше, чем другой, в зависимости от того, что хочет пользователь.
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можно проверить, но ни одна из моих программ не выводит ничего с этим, так что я не могу точно сказать, полезно ли это. Он должен в список «инициализируются (ненулевые) данных».

jippie
источник
Или вы могли бы просто использовать avr-size. Но это не покажет вам динамическое распределение или использование стека.
Игнасио Васкес-Абрамс
@ IgnacioVazquez-Abrams о динамике, то же самое для моего решения. Отредактировал мой ответ.
Джиппи
Хорошо, это самый интересный ответ на данный момент. Я экспериментировал avr-objdumpи avr-sizeвскоре отредактирую свой пост выше. Спасибо за это.
Мадивад
3

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

Было бы лучше использовать комбинацию ручной оценки и с использованием sizeofоператора. Если все ваши объявления статичны, то это должно дать вам точную картину.

Если вы используете динамическое распределение, вы можете столкнуться с проблемой, как только начнете освобождать память. Это связано с фрагментацией памяти в куче.

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

Перечисление занимает столько же места, сколько и int. Итак, если у вас есть набор из 10 элементов в enumобъявлении, это будет 10*sizeof(int). Кроме того, каждая переменная, которая использует перечисление, является просто int.

Для структур было бы легче использовать, sizeofчтобы узнать. Структуры занимают (минимальное) пространство, равное сумме его членов. Если компилятор выполняет выравнивание структуры, это может быть больше, однако это вряд ли в случае avr-gcc.

asheeshr
источник
Я статически назначаю все, насколько я могу. Я никогда не думал об использовании sizeofдля этой цели. На данный момент у меня уже есть почти 400 байтов (глобально). Теперь я пройду и вычислю перечисления (вручную) и структуры (которых у меня есть несколько - и я буду использовать sizeof), и доложу.
Мадивад
Не уверен, что вам действительно нужно sizeofзнать размер ваших статических данных, поскольку они распечатываются avrdude IIRC.
jfpoilpret
@jfpoilpret Это зависит от версии, я думаю. Не все версии и платформы предоставляют это. Мой (Linux, несколько версий) не показывает использование памяти для одного, в то время как версии для Mac.
asheeshr
Я искал подробный вывод, я думал, что это должно быть там, это не так
Мадивад
@AsheeshR Я не знал об этом, мой работает нормально на Windows.
jfpoilpret
1

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

Arduino Builder

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

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

sa_leinad
источник