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

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

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

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

Ответ 1

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

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

Ответ 2

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

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

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;
    // ...
}

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

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

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

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

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

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

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

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

Ответ 5

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

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

  • Проверьте использование стека в предполагаемой высокой точке в самом коде, войдите в интерфейс отладчика.
  • Поместите кепку на основе предполагаемого использования стека с этой крышкой на месте. например ограничить соединения с сервером.