Возможно ли расширение стековой области в памяти?
- Войдите на сайт для отправки комментариев
Чт, 20/10/2016 - 16:42
Здравствуйте, знатоки!
Столкнулся со следующей проблемой (Arduino Leonardo). После компиляции скетча получил сообщение:
Global variables use 1 551 bytes (60%) of dynamic memory, leaving 1 009 bytes for local variables. Maximum is 2 560 bytes.
Но в работе прога выдаёт нечто, наводящее на мысль, что стек проваливается в область переменных, разрушая программу (имеется много динамических массивов). Есть ли выход? Предложения типа "об колено" или "в утиль" уже приняты, прошу не повторяться.
Спасибо.
попробуйте в строке 20 заменить тип переменный с int на byte.
У меня что-то не так с чувством юмора, или что Вы имели в виду под "строкой 20"?
К сожалению, где можно, везде поменял типы на минимально возможные, все сообщения и константы перенёс во флеш-память (PROGMEM).
Может, можно как-то задействовать кусок ROM для RAM?
Спасибо.
У меня что-то не так с чувством юмора, или что Вы имели в виду под "строкой 20"?
строка кода, который ты опубликовал.
А я что-то публиковал?
Что-то Вы меня совсем запутали...
У меня до строки 167 идёт поясняющий текст со ссылками /*...*/.
У меня хрустальный шар плохо работает, строку 200 с 20 перепутал, мой косяк....
В общем скеч то выложите... А то эти шары очень не точные... или он опять "секретный"?
В общем скеч то выложите... А то эти шары очень не точные... или он опять "секретный"?
я запрещаю публиковать код - зрите в хрустальные шары лентяи
Опять не понял, что значит "опять секретный", ну, да ладно.
Поскольку скетч занял почти 3000 строк, дабы не разбежались читающие, привожу фрагменты программы, и скриншоты вывода на монитор.
Подскажите, как выложить два рисунка, пжлст.
P.S. Перепутавших форум с утренником в детсаде попрошу более не напрягаться. Спасибо.
Опять не понял, что значит "опять секретный", ну, да ладно.
"Опять" это значит что некоторые чудаки боятся выкладывать скетч, или как они его называют "код" дабы его никто не спёр и не использовал их "гениальных" ренений во своё обогащение. Ну есть такие, даже среди моих знакомых. Он придумал какую то хрень и даже по телефону обсуждать боится, чтобы не подслушали и не использовали раньше него. И поэтому они начинают задавать абстрактные вопросы, на которые естественно в лучшем случае получат абстрактные ответы. Поэтому дабы помочь пытливым приходится тролить.
Поскольку скетч занял почти 3000 строк, дабы не разбежались читающие, привожу фрагменты программы, и скриншоты вывода на монитор.
Лучше уж сразу всю правду матку в лицо, кто то испугается и убежит, а кто то подскажет где косяк, кто то подскажет более правильное решение.
Подскажите, как выложить два рисунка, пжлст.
Я сейчас в телефона и интерфейс урезан, но там есть кнопочка сверху, рядом с той с которой скетч вставлять нужно, смысл в том что сначала их нужно залить на сервер сайта, потом уже на них ссылаться. Ну я так делаю, может еще как то можно.
P.S. Перепутавших форум с утренником в детсаде попрошу более не напрягаться. Спасибо.
Да мы даже не напрягаемся, нам это в радость.))))
По поводу скетча я лично хз, сейчас профи глянут отпишутся, но по ощущениям Вы пытаетесь "впихнуть невпихуемое". Может есть возможность пожертвовать качеством входных данных дабы уменьшить их объем...
http://arduino.ru/forum/obshchii/sokhranenie-kartinok-na-forume#comment-156836
Но в работе прога выдаёт нечто, наводящее на мысль, что стек проваливается в область переменных, разрушая программу (имеется много динамических массивов). Есть ли выход?
Выхода два:
1. Пересмотреть структуру программы с целью уменьшить суммарный объем используемой статической и динамической памяти. Например, полностью отказаться от использования динамических массивов, а использовать во всех подпрограммах единственный статический массив.
2. Если 1 не привело к успеху, рассмотреть возможность использованием контроллера с бОльшим ресурсом.
Но для начала следовало бы убедиться в том, что первоначальный диагноз (нехватка динамической памяти) поставлен верно. Заодно это поможет и сформулировать стратегию для 1.
PS. Лучше свернуть 3000 строк кода, чем помещать 200, не сворачивая.
Вопрос. Сколько памяти остается если не использовать динамические массивы, а сразу объявить все массивы по максимуму? В приведенных строках нет динамики. Массивы создаются константами. Так в чём будет разница?
http://arduino.ru/forum/obshchii/sokhranenie-kartinok-na-forume#comment-156836
Спасибо! Не догадался, что в опции "Выбор на сервере" возможно закачать с ПК.
Выхода два:
1. Пересмотреть структуру программы с целью уменьшить суммарный объем используемой статической и динамической памяти. Например, полностью отказаться от использования динамических массивов, а использовать во всех подпрограммах единственный статический массив.
2. Если 1 не привело к успеху, рассмотреть возможность использованием контроллера с бОльшим ресурсом.
Но для начала следовало бы убедиться в том, что первоначальный диагноз (нехватка динамической памяти) поставлен верно. Заодно это поможет и сформулировать стратегию для 1.
Я пробовал отказываться от динамических массивов. Пришёл к выводу, что при этом уменьшается загрузка стека, но увеличивается загрузка статической памяти, а поскольку они "двигаются" навстречу друг другу, получается "то на то". Увы.
Менять контроллер - понятно, уже стоит "на повестке дня", но необходимо что-то придумать сейчас на конкретный образец.
"Первоначальный диагноз", похоже, верен, поскольку в массив "залезают" значения введенных ранее переменных, на рис.1 в 5-й строке "0.9265274047", например.
Повторюсь, нельзя ли как-то "сэмулировать" дополнительный кусок оперативки в ROM? Что-то наподобие использования PROGMEM для констант. Спрошу ещё глупее: или присобачить какую-нибудь приблуду на плате для чего-то вроде XMEM (я понимаю, что у меня не ATmega128)?
Спасибо
В 3000 строк кода, думаю конечно же героев разбираться вот так вот запросто, Вы врядли найдете. Поэтому только советы "общего плана":
1. Попробуйте перенести ВСЕ ваши массивы и ВСЕ переменные в глобальную область видимости. В память влезают? Под стек остается достаточно места? Оценить не сложно: смотрите самый глубоко вложенный кусок окда и считаете сколько потребуется места на стеке, памятуя что там "в среднем" окажется кроме 2 байт на точку возврата и по 2-4 байта на параметр (в т.ч. и БАЙТОВЫЙ!) плюсом 2-10 байт на сохранение регистров МК в зависимости от размеров функций и количества локалов в них. Если локалы совместно занимают более 6-10 байт, все остальные тоже плюсуйте к объему стека, так - надежней. Влезает?
Если нет, но "почти", то попробуйте уменьшить количество глобальных/локальных/вложенных_функций тем или иным способом. Варианты:
а) перевод локалов в глобалы с целью уменьшения количества таскаемых параметров по стеку "туда-сюда"; б) совмещение глобалов, которые не требуются одновременно, хотя бы через unit {}; в) развертывание inline (места под программу у вас хватает, верно?) небольших функций и уменьшение вложенности вызовов. г) Перевод глобалов в параметры. последнее несколько противоречит "а", но применяется для разных ситуаций: если глобал постоянно "перевычисляется" в некой функции и используется рядом, то может оказаться что его перевод в "параметры" и смена его времени жизни наоборот сократит требования к памяти .. в общем "не всё так однозначно".
Если нет и "много" - ищите иной камень с большей памятью или присмотритесь к варианту "ATmega128A + расширение SRAM". Это может оказаться как раз "ваш случай". Тут есть в разделе "Общий" пример успешного решения для расширения SRAM на +32 килобайт и мой проект расширителя на +512 килобайт. Но такие расширители или платы в с памятью Вам придется изготавливать практически самостоятельно. Сможете? Мне моя первая попытка сделать расширитель +64кб для Мега2560 окончилась полным фиаско, но я был слишком самонадеян .. :)
***
Благодарю за предложенные рекомендации. По большей части это я уже проделал. Похоже, ситуация патовая. Разве что в рамках конкретного образца снизить точность (перейти с float на int).
Отдельная благодарность за подсказку в работе с расширителями, надо поработать в этом направлении.
Спасибо.
Я пробовал отказываться от динамических массивов. Пришёл к выводу, что при этом уменьшается загрузка стека, но увеличивается загрузка статической памяти, а поскольку они "двигаются" навстречу друг другу, получается "то на то". Увы.
Еще раз.
Пересмотрите организацию программы и определите, сколько памяти Вам надо. Зачастую бывает, что один и тот же кусок памяти можно поочередно использовать разными подпрограммамиЮ, а иногда - нет, все данные должны храниться в памяти одновременно.
Вот определитесь, сколько именно памяти Вам нужно одновременно. Если окажется, что больше 2.0-2.2к - Вы неверно выбрали "камень".
Повторюсь, нельзя ли как-то "сэмулировать" дополнительный кусок оперативки в ROM? Что-то наподобие использования PROGMEM для констант. Спрошу ещё глупее: или присобачить какую-нибудь приблуду на плате для чего-то вроде XMEM (я понимаю, что у меня не ATmega128)?
Нет.
В принципе, "сэмулировать" можно при помощи SSD-карточки, если время от времени сбрасывать на нее часть не используемых в данный момент данных, а потом читать.
В ПЗУ создать фрагмент ОЗУ нельзя. Никак.
В принципе, "сэмулировать" можно при помощи SSD-карточки, если время от времени сбрасывать на нее часть не используемых в данный момент данных, а потом читать.
А можно с этого места по-подробнее?
Спасибо.
Ещё можно запустить программу в Proteus и посмотреть в чём там дело в отладчике.
Ещё можно запустить программу в Proteus и посмотреть в чём там дело в отладчике.
Имею неудачный опыт работы с серьёзными программами в Proteus. Но Вы подсказали мне, что не лишне повторить эксперимент, возможно, ранее я был менее внимателен, что-то упустил.
Спасибо.
Ну .. если у Вас float данные .. то не ждите "чуда". Проще поменять камень, если не умеете обходится int или long вместо плавающей точки ..
Нужно просто уметь его готовить. Я использую Proteus 7.7 (насколько мне помнится). Да, есть некоторые ограничения, но в вашем случае, думается, программа может быть отлажена.
Такие проблемы как у вас ищутся как раз при помощи отладчика, если он есть.
Ну .. если у Вас float данные .. то не ждите "чуда". Проще поменять камень, если не умеете обходится int или long вместо плавающей точки ..
long и float занимают одинаковые 4 байта памяти. Чуда здесь нет. Перевод float в int приведёт к ещё бОльшей потери памяти, если не пойти на снижение точности.
Чудеса творить, не умею.
Спасибо.
Нужно просто уметь его готовить. Я использую Proteus 7.7 (насколько мне помнится). Да, есть некоторые ограничения, но в вашем случае, думается, программа может быть отлажена.
Такие проблемы как у вас ищутся как раз при помощи отладчика, если он есть.
Спасибо, обязательно попробую. У меня тоже Proteus 7.7, правда, "взломанный".
Да, конечно, но за счёт кучи, разумеется. Надо?
Вам точно нужен long? Насколько мне известно, все датчики выдают результат в каких-то своих "попугаях", но как правило это 6,8,10,12,16 и т.д. но бит. То бишь числа - целые. Кто вам мешает проводить вычисления в этих же попугаях, а на показ преобразовывать результат к вещественному числу, если оно требуется? Кстати, если датчик выдает до 16 бит, то целочисленные вычисления могут даже повысить вашу точность вычислений, а про их ускорение я уж имолчу вовсе. Я думаю что в природе наверное есть такие датчики, которые выдают вещественное число, но мне с ними сталкиваться как-то не приходилось в рамках Ардуино.
Исключительно как пример, смотрите: узв. датчик расстояний выдает .. не, не расстояние, а время прохождения сигнала "туда и обратно" .. упс. В целочисленных микросекундах. Для его пересчета часто используют традиционный делитель 59, хотя для нормальных условий скорость звука равна 335м/сек и точное значение 59.70... Казалось бы вот оно то место, где требуется вещественная арифметика. А теперь, если мы немножко поварим головой, то обнаружим что искомое преобразование легко выполняется .. умножением времени в микросекундах на 343 и последующим его делением на 2048 (это сдвиг на 11 разрядов, что достаточно быстро) и .. получаем точность преобразования не хуже 0.1мм на всем диапазоне расстояний этого датчика. И это при том, что сам датчик измеряет время сигнала с точностью 1мм согласно даташиту.
Аналогично, положение серводвигателей задается углом поворота, который для человеков удобно представлять градусами и их долями (вещ. число), а серва понимает .. время выданной "1" в периоде ШИМ (скважность сигнала), которое хранится в 8 или 16 битах. И 8 бит часто хватает "с головой" согласно точности фиксации угла серводвигателем под нагрузкой и без таковой.
Сдается мне, что проблема не столько в памяти, а отсутствии опыта вести расчеты в датчиковых попугаях.
Если хотите, я могу создать мелкую виртуальную машину для целей отладки.
На борту можно иметь: Windows XP Mini 32-bit, Proteus 7.7, Arduino SDK (какой-то версии), Arduino-Makefile (для сборки проектов через свой Makefile, т.е. без использования Arduino IDE), PuTTY, VSPD, Far, Total Commander.
Для каждой платы Arduino можно создать свою простую схему отладки. Обычно это сам мк и терминал.
Да, конечно, но за счёт кучи, разумеется. Надо?
Динамические массивы в "куче", если я правильно понимаю.
Подскажите, как за счёт "кучи".
Спасибо.
Сдается мне, что проблема не столько в памяти, а отсутствии опыта вести расчеты в датчиковых попугаях.
У меня сложные математические расчёты, поэтому, "попугаи" от датчиков в int разрастаются в динозавров в float. А требования к точности в конечной информации весьма велики.
Спасибо.
Если хотите, я могу создать мелкую виртуальную машину для целей отладки.
Честно говоря, плохо понимаю, о чём речь. Если можно, по-подробнее.
Спасибо.
Можно создать виртуальный компьютер (VMWare или VirtualBox), на котором установить всё что нужно для отладки кода Arduino в Proteus. Обычным ардуинщикам лень изучать всё это отладочное окружение, а зря. Процентов 90 вопросов местных отпали бы сами собой, если бы человек увидел живьём как работает его программа или её часть.
Сложные проекты с ethernet платой или чем-то подобным отлаживать так нельзя, но очень многие - можно. Также можно виртуальный контроллер подключать через последовательные порты к реальным устройствам.
Я могу создать такую виртуальную машину и наполнить её тем, чем я обычно пользуюсь при отладке.
Подскажите, как за счёт "кучи".
Вам доступны следующие переменные для управления размером кучи (и, соответсвенно стека)
Для того, чтобы получить к ним доступ, просто объявите их у себя как extern. Также Вам доступен текущий указатель стека - SP (менять его не стоит, но для чтения он вполне доступен).
При помощи этих переменных Вы можете манипулировать размером кучи, т.е. делить память между кучей и стэком.
Для контроля "что получается", можете воспользоваться простой программкой (библиотекой. если Вам так нравится), которая печатает сведения о текущей загрузке памяти. Она состоит из файлов
MemoryExplorer.h
и MemoryExplorer.cpp
Там есть функция memoryReport, которая распечатает и доступную кучу, и список свободных областей памяти.
Вот пример её использовниая - MemoryExplorer_Test.ino
Библиотечка сыровата и только делаетеся, поэтому возможны глупые ляпы, но так, вроде работает, попробуйте.
Попробуйте поманипулировать переменными.
Только имейте в виду, что куча безопаснее стека, т.к. при выделении памяти в куче производится контроль на её переполнение и, если что, Вам просто не выделаят память. А вот стек растёт бесконтрольно и запросто может пропороть собою кучу.
А как быть с фрагментацией кучи?
А как быть с фрагментацией кучи?
Это Вопрос ко мне? Или к ТС?
Когда что-то предлагаешь, нужно писать плюсы и минусы такого подхода. Если программа на мк будет произвольно с кучей работать, то рано или поздно куча может закончится из-за её фрагментации. Короче говоря, безопасно можно использовать кучу только при старте программы (тогда её использование не отличается от глобальных переменных). В циклах можно использовать выделение и освобождение только если размеры блоков одинаковые и вы точно уверены что выделенный блок освободиться, при следующем выделении вернётся указатель на этот блок памяти.
В общем и целом, на мк нежелательно использовать кучу обычным для ПК способом, а если вы её используете, то должны точно понимать к чему это может приводить.
Вот список правил, которых нужно придерживаться (либо писать дефрагментатор и вызывать его при каждом выделении памяти):
there seems to be a few usage patterns that are safe from the point of view of heap fragmentation
Нужно ещё раз подумать: А действительно ли нужна куча в моей программе?
Ещё одна ссылка: Optimizing SRAM
Я то-то предлагаю?
Я вего лишь ответил на вопрос как "переделить" память между кучей и стеком.
Экскурс в проблемы фрагментации - полный офф-топ в данной теме, давайте прекратим его едва начав, ладно?
Кто это сказал, что оффтоп? Я вижу код:
Вы гарантируете, что он не может привести к проблемам с фрагментацией кучи? А если есть ещё такие места с выделением памяти? Их взаимодействие не может привести к фрагментации кучи?
Что означает этот комментарий?
/* May be changed by the user only before the first malloc() call. */
Куча может расти? Если прибавить к этому фрагментацию, то получим третью картинку - наложение областей памяти. Имеет это отношение к теме? Имеет и самое прямое, пока явно не доказано обратное.
А это самое обратное можно получить путём отладки. Раз не получается через терминал, то нужны более мощные средства - отладчик, либо не пользоваться динамической памятью.
Peotr, выведите в терминал адреса, получаемые при динамическом выделении памяти (до момента появления ошибок).
Peotr, выведите в терминал адреса, получаемые при динамическом выделении памяти (до момента появления ошибок).
К сожалению, не знаю, как это оганизовать. Или не достаточно правильно понял вопрос. Под "терминалом" понимается обычный программный терминал для com-порта?
Извините за тупость.
Я могу создать такую виртуальную машину...
Я с подобным не имел дела, поэтому плохо представляю этот инструмент. Хорошо бы опробовать. А сколько стОит такая машина?
Вам доступны следующие переменные для управления размером кучи (и, соответсвенно стека)
Спасибо, очень интересный материал для глубокого анализа. Обязательно займусь.
Я не смотрел исходники функции malloc(). Вариантов её работы может быть много. Чтобы глубоко не копать можно просто посмотреть адреса, которые возвращает функция с течением времени (до момента появления ошибок). Обычно это делается при помощи printf() так:
printf( "addr = 0x%04X", addr );
Вы должны вывести значение указателя с помощью Serial.print(). Я использую свою функцию для вывода, она сложная и тут будет не уместна. Вы можете сделать что-то вроде: Serial.println( Matr, HEX ) там, где получаете этот указатель.
Если работа с кучей у вас сбалансирована, то значения указателей при прочих равных условиях не должны расти. Тут много "если", т.к. не известно что там ваш код вообще делает.
Кто это сказал, что оффтоп?
Я.
Я с подобным не имел дела, поэтому плохо представляю этот инструмент. Хорошо бы опробовать. А сколько стОит такая машина?
Трудно ответить на этот вопрос. Она полностью будет набита пиратскими программами. Легальная стоимость не известна, а вообще она конечно бесплатная.
Трудно ответить на этот вопрос.
Трудно понять этот трудный ответ. :))))))))))))
Peotr, Вы, конечно, можете увеличить стек за счёт кучи, только это вряд-ли Вам поможет. Дело в том, что елси у Вас перепоняется стек, значит он налазит на кучу. Сумма памяти под стек и кучу всё равно константа. Ну, увеличите Вы стек (уменьшив кучу) если им двоим не хватаем места, то и не будет хватать. Лучше внимательно помсотреть на программу.
В принципе, тонике манипуляции стеком и кучей иногда поволяют пройти по тонкой грани и виртуозно вписаться в память там, где без этих манипуляций ничего бы не вышло. Но это не для новичков в программировании. Это высшая лига. Вы игрок высшей лиги? ну, тогда пробуйте.
Кстати, та библиотека, что я Вам дал печатает список свободных областей, т.е. это тот самый случай, когда Вы можете просто понаблюдать за фрагментацией и заполнением и, возможно, понять, что не так.
Вот, например, не знаю, как Вы, но многие здесь любят использовать класс String по любому поводу (просто, чтобы число в строку преобразовать, например), а между прочим String - это просто убийца памяти. Особенно ужасно, кода люди умудряются передавать пременные типа String в качестве параметров по значению - это попросту приводит к дублированию памяти выделенной - под String - ради передачи пераметра создаётся ещё одна копия уже имеющейся строки - там ужас, что творится.
> Трудно понять этот трудный ответ. :))))))))))))
Вот это уже оффтоп, если не обсуждается сам процесс отладки вашей программы.
Если есть интерес, я создам отдельную тему на этот счёт. У меня на ПК "развёрнута" такая машина и я пользуюсь её функционалом, в т.ч. для ответов здесь (если полностью приведён код программы).
К примеру, я промоделировал работу функции вывода цвета для RGB светодиода. Выглядит это так:
Мк может быть любой, на картинке это ATMega2560.
Peotr, Вы, конечно, можете увеличить стек за счёт кучи, только это вряд-ли Вам поможет.
Извините, но я сразу понял, что перераспределение купюр по карманам их общую сумму не изменит. Но мне понравился предложенный анализ памяти, чем я обязательно воспользуюсь для более глубокого понимания состояния памяти контроллера.
Стринги, println и прочие излишества в этой проге давно ликвидировал, как класс. :)
В принципе, "сэмулировать" можно при помощи SSD-карточки, если время от времени сбрасывать на нее часть не используемых в данный момент данных, а потом читать.
А можно с этого места по-подробнее?
Спасибо.
Предположим, Вы уже проанализировали распределение памяти в Вашей программе и пришли к выводу, что с одной стороны все хранящиеся в памяти массивы Вам необходимы и их данные актуальны (т.е. нельзя что-нибудь стереть), а с другой - есть необходимость выделить новый массив, но места для него нет.
В этом случае часть данных из тех, что не используется в данный момент (но обязательно понадибится позже - иначе из можно было бы просто стереть), сбрасывавется в файл на SD, а на освободтвшемся месте размещается требуемый массив. Когда надобность в новом массиве отпадет, данные читаются с SD на прежнее место.