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


102

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

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

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

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


6
Размер стека по умолчанию для 64-битных процессов составляет 4 МБ, для 32-битных процессов - 1 МБ. Вы можете изменить размер стека основных потоков, изменив значение в его PE-заголовке. Вы также можете указать размер стека, используя правильную перегрузку Threadконструктора. НО, возникает вопрос, зачем вам стек побольше?
Юваль Ицчаков

2
Спасибо, отредактировал. :) Вопрос не в том, как использовать больший размер стека, а в том, почему размер стека решен равным 1 МБ (4 МБ) .
Николай Костов

8
Потому что каждый поток по умолчанию получит этот размер стека, а большинству потоков это не нужно. Я только что загрузил свой компьютер, и в настоящее время в системе выполняется 1200 потоков. А теперь
посчитайте

2
@LucasTrzesniewski Не только это, это должно быть заразительно в памяти . Обратите внимание: чем больше размер стека, тем меньше потоков может создать ваш процесс в своем виртуальном адресном пространстве.
Юваль Ицчаков

Не уверен насчет «точно» 1 МБ: в моей Windows 8.1 консольное приложение .NET Core 3.1 имеет 1572864размер стека в байтах по умолчанию (получено с помощью GetCurrentThreadStackLimits Win32 API). Я могу stackallocпримерно 1500000байты без StackOverflowException.
Георгий Чахидзе

Ответы:


210

введите описание изображения здесь

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

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

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

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

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

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

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

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


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


3
относительно вашего комментария The only way to allocate space on the stack with C# is with the unsafe stackalloc keyword.- Локальные переменные, например, intобъявленные внутри метода, не хранятся в стеке? Я так думаю.
RBT

2
Хорошо. Теперь я понял, что стек-фрейм - не единственный выбор хранилища для локальных переменных функции. Его можно сохранить во фрейме стека, как это предлагается в одном из пунктов вашего маркера. Очень поучительно, Ганс. Я не могу сказать достаточно благодарности за написание таких проницательных постов. Честно говоря, стек - это такая большая абстракция для программирования в целом, чтобы избежать ненужной сложности.
RBT

Очень подробное описание @Hans. Мне просто было интересно, каково минимальное возможное значение maxStackSizeпотока? Мне не удалось найти его на [MSDN] ( msdn.microsoft.com/en-us/library/5cykbwz4(v=vs.110).aspx ). Основываясь на ваших комментариях, кажется, что использование стека является абсолютным минимумом, и я могу использовать наименьшее значение для размещения максимально возможных потоков. Спасибо.
MKR

1
@KFL: Вы можете легко ответить на свой вопрос, попробовав его!
Эрик Липперт

1
Если поведение по умолчанию больше не фиксирует стек, то этот файл уценки необходимо отредактировать github.com/dotnet/docs/blob/master/docs/framework/…
Джон Стюиен

5

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

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

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

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

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

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

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

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

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

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

Источники:

  1. Размер стека потоков (Microsoft Docs)
  2. Выходя за рамки Windows: процессы и потоки (Марк Руссинович)
  3. По умолчанию максимальный размер стека потока, созданного в собственном процессе IIS, составляет 256 КБ (KB932909).

Если мне нужен больший размер стека, я могу его установить ( atalasoft.com/cs/blogs/rickm/archive/2008/04/22/… ). Я хочу знать соображения и причины этого решения.
Николай Костов

2
Ладно. Теперь я вас понимаю :) Размер стека по умолчанию должен быть оптимальным (см. Комментарий @Lucas Trzesniewski) и должен быть округлен до ближайшего кратного степени детализации распределения. Если указанный размер стека больше, чем размер стека по умолчанию, он округляется до ближайшего кратного 1 МБ. Поэтому Microsoft выбрала эти размеры в качестве размеров стека по умолчанию для всех приложений пользовательского режима. И других причин нет.
Yoh Deadfall

Есть источники? Есть документация? :)
Николай Костов

@Yoh интересная ссылка. Вы должны обобщить это в своем ответе.
Лукас Тшесневски
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.