Почему размер стека в С# равен 1 МБ?

Сегодня ПК имеют большой объем физической памяти, но все же размер стека С# составляет всего 1 МБ для 32-битных процессов и 4 МБ для 64-битных процессов (Стековая емкость в С#).

Почему размер стека в CLR все еще так ограничен?

И почему именно 1 МБ (4 МБ) (а не 2 МБ или 512 КБ)? Почему было решено использовать эти суммы?

Меня интересуют соображения и причины этого решения.

Ответ 1

enter image description here

Вы смотрите на парня, который сделал этот выбор. Дэвид Катлер и его команда выбрали один мегабайт в качестве размера стека по умолчанию. Ничего общего с .NET или С#, это было прибито, когда они создали Windows NT. Один мегабайт - это то, что он выбирает, когда заголовок EXE программы или вызов winapi CreateThread() не задают размер стека явно. Что является нормальным способом, почти любой программист оставляет его ОС для выбора размера.

Этот выбор, вероятно, предшествует дизайну Windows NT, история слишком мутная об этом. Было бы хорошо, если бы Катлер написал бы книгу об этом, но он никогда не был писателем. Он был чрезвычайно влиятелен на то, как работают компьютеры. Его первый дизайн ОС - RSX-11M, 16-разрядная операционная система для компьютеров DEC (Digital Equipment Corporation). Это сильно повлияло на Gary Kildall CP/M, первую достойную ОС для 8-битных микропроцессоров. Что сильно повлияло на MS-DOS.

Следующим его проектом стала VMS, операционная система для 32-разрядных процессоров с поддержкой виртуальной памяти. Очень успешный. Его следующий был отменен DEC примерно в то время, когда компания начала распадаться, не имея возможности конкурировать с дешевым оборудованием для ПК. Кий Microsoft, они сделали ему предложение, от которого он не мог отказаться. К нему присоединились и многие из его коллег. Они работали над VMS v2, более известной как Windows NT. DEC расстроилась из-за этого, деньги сменили руки, чтобы урегулировать это. Является ли VMS уже одним мегабайтом, я не знаю, я знаю только RSX-11. Это маловероятно.

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

Это лишнее чрезмерное значение в программе .NET, потому что один мегабайт был первоначально выбран для размещения собственных программ. Они, как правило, создают большие стековые фреймы, сохраняя строки и буферы (массивы) в стеке. Печально известный как вектор атаки вредоносного ПО, переполнение буфера может манипулировать программой данными. Не так, как работают .NET-программы, строки и массивы выделяются на куче GC и проверяется индексация. Единственный способ выделить пространство в стеке с помощью С# - это небезопасное ключевое слово stackalloc.

Единственное нетривиальное использование стека в .NET - это дрожание. Он использует стек вашего потока для компиляции MSIL для компиляции MSIL в машинный код. Я никогда не видел и не проверял, сколько места он требует, скорее зависит от характера кода и от того, включен ли оптимизатор, но несколько десятков килобайт - это грубое предположение. В противном случае, как этот сайт получил свое имя, переполнение стека в .NET-программе является довольно фатальным. Недостаточно свободного места (менее 3 килобайт), чтобы все еще надежно использовать JIT любой код, который пытается поймать исключение. Kaboom для настольных компьютеров является единственным вариантом.

И последнее, но не менее важное:.NET-программа делает что-то довольно непродуктивное со стеком. CLR зафиксирует стек потока. Это дорогое слово, что означает, что он не просто резервирует размер стека, но также гарантирует, что пространство зарезервировано в файле подкачки операционной системы, поэтому при необходимости стек всегда может быть заменен. Неспособность совершить является фатальной ошибкой и безоговорочно завершает программу. Это происходит только на машине с очень маленькой ОЗУ, которая запускает полностью слишком много процессов, такая машина превратится в патоку, прежде чем программы начнут умирать. Возможная проблема 15 лет назад, а не сегодня. Программисты, которые настраивают свою программу, чтобы действовать как гоночный автомобиль F1, используют <disableCommitThreadStack> в своем файле .config.

Fwiw, Катлер не прекратил разработку операционных систем. Эта фотография была сделана, когда он работал над Azure.


Обновление, я заметил, что .NET больше не выполняет стек. Не совсем точно, когда и почему это произошло, я слишком долго не проверял. Я предполагаю, что это изменение дизайна произошло где-то около .NET 4.5. Довольно разумные изменения.

Ответ 2

Размер зарезервированного стека по умолчанию указан компоновщиком, и он может быть переопределен разработчиками путем изменения значения PE во время ссылки или для отдельного потока, указав параметр dwStackSize для функции CreateThread WinAPI.

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

Почему значение равно 1 МБ для 32-битных процессов и 4 МБ для 64-битного? Я думаю, вы должны спросить разработчиков, которые разрабатывали Windows, или ждать, пока кто-то из них ответит на ваш вопрос.

Возможно, Марк Руссинович знает это, и вы можете contact его. Возможно, вы можете найти эту информацию в своих книгах Windows Internals раньше, чем в шестом издании, в котором описывается меньше информации о стеках, а не о статье . Или, может быть, Раймонд Чен знает причины, потому что он пишет интересные вещи о внутренностях Windows и ее истории. Он также может ответить на ваш вопрос, но вы должны отправить предложение в Блок предложений.

Но в это время я попытаюсь объяснить некоторые возможные причины, по которым Microsoft выбирает эти значения, используя блоги MSDN, Mark и Raymond.

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

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

В настоящее время эти значения в основном используются для обратной совместимости, потому что структуры, которые передаются как параметры для функций WinAPI, по-прежнему распределяются в стеке. Но если вы не используете распределения стека, то использование стека потоков будет значительно меньше, чем значение по умолчанию 1 МБ, и это расточительно, как упоминал Ханс Пассант. И для предотвращения этого ОС фиксирует только первую страницу стека (4 КБ), если другой не указан в заголовке PE приложения. Другие страницы предоставляются по запросу.

Некоторые приложения переопределяют зарезервированное адресное пространство и первоначально исправляют оптимизацию использования памяти. В качестве примера максимальный размер стека в потоке процесса IIS составляет 256 КБ (KB932909). И это уменьшение значений по умолчанию рекомендуется Microsoft:

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

Источники: