Как определить максимальное использование стека?

Какие методы доступны для определения оптимального размера стека для встроенной системы / системы с ограничениями памяти? Если он слишком большой, память тратится впустую, которую можно было бы использовать в другом месте. Однако, если он будет слишком маленьким, мы получим тезку этого сайта ...

Чтобы попытаться быстро начать что-то: Джек Гэнссл заявляет в Искусство конструирования встроенных систем, что: «Имея опыт, человек узнает стандартный научный способ вычисления правильного размера для стека: выберите размер наугад и надейтесь». Может ли кто-нибудь сделать лучше?

Был запрошен более конкретный пример. Итак, как насчет программы на C, ориентированной на MCU MSP430 с 2 КБ ОЗУ, используя Набор инструментов IAR Embedded Workbench без операционной системы? Эта IDE может отображать содержимое стека и его использование при использовании отладчика JTAG.


person Judge Maygarden    schedule 23.12.2008    source источник
comment
зависит от используемого вами набора микросхем / ОС / языка программирования.   -  person Ady    schedule 23.12.2008
comment
Рад видеть, что у этого вопроса есть ответы, в отличие от stackoverflow.com/questions/177516/   -  person Constantin    schedule 23.12.2008
comment
Я видел этот вопрос, когда задавал вопрос, но подумал, что встроенный наклон разделяет их ...   -  person Judge Maygarden    schedule 23.12.2008
comment
Джек Гэнссл говорит не только об этом. Думаю, это было просто его вступительное слово. Из его книги, 2-е издание, стр. 250: Поскольку у немногих программистов есть разумный способ определить максимальные требования к стеку, всегда предполагайте, что ваши оценки будут неверными. Для каждого стека в системе убедитесь, что код инициализации заполняет весь объем памяти, выделенной для стека, значением 0x55. Позже, при отладке, вы можете просмотреть стек и обнаружить переполнение стека, не увидев в этом регионе блоков размером 0x55 ...   -  person Craig McQueen    schedule 19.11.2009
comment
Я знаю, что это еще не все, что он говорит, но я пытался использовать то же, что и он: вызвать интерес к теме. ;)   -  person Judge Maygarden    schedule 19.11.2009


Ответы (5)


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

Именно так IAR IDE определяет количество используемого стека.

person Michael Burr    schedule 23.12.2008
comment
Правда заполняет стек на 0xCD. - person Judge Maygarden; 23.12.2008
comment
Я использовал эту технику и раньше. Хитрость заключается в том, чтобы нарисовать стек в pre-main (или в main из точки сразу после вашего текущего кадра стека) до вашего предела, а затем отсчитывать назад от предела стека до места, где окрашенная память испорчена (не 0xCD) - person JeffV; 23.12.2008
comment
Но как вы на самом деле это делаете? Я задаю аналогичный вопрос об обычном ПК на странице stackoverflow.com/questions/1740888/. - person JXG; 16.11.2009
comment
@JXG - для программы Windows это не так уж и работает; памятью стека управляет ОС, поэтому у вас действительно нет хорошей возможности нарисовать стек. Это решение больше подходит для встраиваемых систем, где стек для задачи часто управляется приложением. Придется подумать, как это безопасно сделать в Windows. - person Michael Burr; 16.11.2009
comment
@Michael - Интересный метод, применяемый IAR IDE и во встроенных системах для определения количества используемого стека. Хорошо, что вы указали, что в Windows стековая память управляется ОС и, следовательно, нет хорошего шанса раскрасить / раскрасить стек! +1. - person Karthik Balaguru; 26.06.2010
comment
@Michael Разве Visual C ++ не рисует вам стек? - person Judge Maygarden; 17.06.2011

Вы отметили свой вопрос статическим анализом, но эту проблему сложно решить с помощью статического анализа. Использование стека зависит от профиля времени выполнения программы, особенно если вы используете рекурсию или alloca. Учитывая, что это встраиваемая платформа, я думаю, также сложно запустить что-то вроде ps или top и посмотреть, сколько стека использует ваше приложение.

Интересный подход - использовать адрес текущего кадра стека, чтобы определить, какой объем стека используется. Вы можете сделать это, взяв адрес аргумента функции или локальной переменной. Сделайте это для функции main и для функций, которые, по вашему мнению, используют наибольший стек. Разница подскажет, какой стек требуется вашему приложению. Вот пример (при обычном росте стека от высокого к низкому).

char *stack_top, stack_bottom;

int
main(int argc, char *argv[])
{
    stack_top = (char *)&argc;
    // ...
    printf("Stack usage: %d\n", stack_top - stack_bottom);
}

void
deeply_nested_function(void)
{
    int a;
    stack_bottom = (char *)&a;
    // ...
}

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

void
stack_measurement_function(void)
{
    int a;
    stack_bottom = min(stack_bottom, (char *)&a);
    // ...
}

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

person Diomidis Spinellis    schedule 23.12.2008
comment
Итак, тестирование во время выполнения - это ответ в ореховой скорлупе? Выбрать большую стопку, а затем уменьшить ее после определения потолка? - person Judge Maygarden; 23.12.2008
comment
Поскольку вы говорите в своем ответе, что эту проблему трудно решить с помощью статического анализа, я упоминаю absint.com / stackanalyzer - person Pascal Cuoq; 19.01.2011

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

Под "хорошим" инструментом анализа я подразумеваю тот, который может читать все задействованные единицы компиляции, может определять прямые вызовы функций, может определять косвенные указатели в модуле компиляции, может вычислять консервативный анализ точек - по всей системе, может построить граф вызовов с учетом анализа точек. Это исключает использование множества инструментов, поэтому можно встретить специальные методы, такие как «заполнить стек во время выполнения и посмотреть, что произойдет». Вам также нужны оценки требований стека, которые компилятор помещает в стек; многое из этого можно приблизительно оценить, просто зная, насколько велики потребности в хранилище для всех типов, что обычно довольно легко определить для программ C встроенных систем. Наконец, вам нужно верить, что ваше приложение не имеет рекурсивных вызовов или что инструмент имеет представление о глубочайшей рекурсии (вероятно, по вашему сообщению).

DMS Software Reengineering Toolkit удовлетворяет всем этим требованиям для программ на языке C. См. http://www.semanticdesigns.com/Products/DMS/DMSToolkit.html Вам по-прежнему необходимо настроить его для вычисления потребности в стеке путем сканирования графа вызовов и использования различных оценок размера.

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

person Ira Baxter    schedule 14.07.2009

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

Однако пара реализаций позволяет немного облегчить сложность:

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

(2) Указатель стека после возврата из каждой функции или вызова подпрограммы должен быть таким же, как и до вызова. Следовательно, максимальный размер стека - это максимальный размер стека по всем операторам программы, достигнутый в каждом операторе. (По крайней мере, это хорошее правило, которое следует соблюдать, иначе у вас будут проблемы!)

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

Это не может быть выполнено до тех пор, пока все вызываемые функции также не будут скомпилированы. Поэтому я создаю файл для каждого скомпилированного модуля, в котором записывается количество размеров стека для каждого оператора (в основном пиковое значение перед каждым вызовом функции и значение непосредственно перед каждым вызовом функции (за исключением любых еще неизвестных добавлений к размеру стека, вызванных функцией call) и имена задействованных функций.Затем я ретроспективно обрабатываю эти файлы с помощью рекурсивной процедуры, как только все функции скомпилированы, чтобы определить пиковый размер стека.

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

Пример: предположим, что функция 1 вызывает функцию 2, и поток программы обеих зависит от значения данных X. Предположим, что существует диапазон X, который заставляет функцию 1 выполнять свой худший оператор, который включает вызов функции 2, которая не выполняется. это утверждение наихудшего случая для того же диапазона X. Поскольку мы вычислили максимально возможный размер стека, используя наихудшие случаи одновременно для функции 1 и функции 2, мы могли переоценить размер стека. По крайней мере, мы ошиблись в большей степени.

Мне нравится давать подпрограммам прерывания свое собственное пространство стека в стеке ОС, если они нужны, чтобы они не добавляли к требованиям стека программ, кроме адреса возврата из прерывания.

person cloggervic    schedule 07.08.2013

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

Затем изучите вручную часть кода, ища, где использование стека, вероятно, будет самым высоким (помните, я сказал, что нет массивов)

  • Проверьте использование стека на подозрительной высокой точке в самом коде, войдите в интерфейс отладчика.
  • Установите колпачок на основе предполагаемого использования стека с этим колпачком на месте. например ограничить серверные подключения.
person unixman83    schedule 27.12.2011
comment
Этот вопрос помечен как встроенный. Поэтому ваш совет никогда не использовать массивы, всегда использовать malloc () особенно неуместен, но на любом оборудовании я бы сказал, что это, как правило, плохой совет. Почему, черт возьми, вы всегда будете выделять только из кучи? - person Morten Jensen; 19.06.2012
comment
Сложно представить, почему вы не удалили этот ответ. - person phonetagger; 21.03.2016
comment
Не давайте ужасных предписывающих советов без каких-либо оснований. - person Craig Barnes; 07.05.2018