Чому розмір стека в C # рівно 1 Мб?


102

Сьогоднішні ПК мають велику кількість фізичної оперативної пам’яті, але все-таки розмір стека C # становить лише 1 Мб для 32-бітних процесів і 4 Мб для 64-бітних процесів ( Ємність стека в C # ).

Чому розмір стека в CLR все ще настільки обмежений?

І чому саме 1 МБ (4 МБ) (а не 2 МБ чи 512 КБ)? Чому було вирішено використовувати ці суми?

Мене цікавлять міркування та причини цього рішення .


6
Розмір стека за замовчуванням для 64-бітних процесів становить 4 МБ, це 1 МБ для 32-бітних процесів. Ви можете змінити розмір стека основних потоків, змінивши значення в його заголовку PE. Ви також можете вказати розмір стека, скориставшись правильним перевантаженням Threadконструктора. АЛЕ, це ставить питання, навіщо вам більший стек?
Юваль Ітчаков

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

8
Оскільки кожна нитка отримає цей розмір стека за замовчуванням, і більшості потоків це не так вже й потрібно. Я щойно завантажив свій ПК, і система зараз працює на 1200 потоків. Тепер зробіть математику;)
Лукас Тшесневський

2
@LucasTrzesniewski Мало того, це має бути заразливим у пам'яті . Зауважте, чим більший розмір стека, тим менше ниток, які ваш процес може створити у своєму віртуальному адресному просторі.
Юваль Ітчаков

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

Відповіді:


210

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

Ви дивитесь на хлопця, який зробив цей вибір. Девід Катлер та його команда вибрали один мегабайт як розмір стека за замовчуванням. Нічого спільного з .NET або C #, це було прибито під час створення Windows NT. Один мегабайт - це те, що він вибирається, коли заголовок EXE програми або виклик CreateThread () winapi не вказує чітко розмір стека. Це нормальний спосіб, майже будь-який програміст залишає його в ОС, щоб вибрати розмір.

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

Наступним його дизайном став VMS, операційна система для 32-бітних процесорів з підтримкою віртуальної пам'яті. Дуже вдало. Наступний його проект був скасований DEC приблизно в той час, коли компанія почала розпадатися, не маючи можливості конкурувати з дешевим обладнанням для ПК. Cue Microsoft, вони зробили йому пропозицію, від якої він не міг відмовити. Багато його співробітників теж приєдналися. Вони працювали над VMS v2, більш відомим як Windows NT. ОВК засмутилася з цього приводу, гроші змінили руки, щоб його врегулювати. Чи вибрано VMS вже один мегабайт, я не знаю, я знаю лише RSX-11 досить добре. Це малоймовірно.

Досить історії. Один мегабайт - це багато , справжня нитка рідко споживає більше ніж пару жменьків кілобайт. Тож мегабайт насправді досить марнотратний. Однак мегабайт - це лише віртуальна пам'ять . Просто числа в процесор, по одному на кожні 4096 байт. Ви ніколи фактично не використовуєте фізичну пам'ять, оперативну пам’ять в апараті, поки ви фактично не вирішите це питання.

Це дуже надмірно в програмі .NET, оскільки розмір одного мегабайти спочатку був підібраний для розміщення нативних програм. Які, як правило, створюють великі кадри стека, зберігаючи також рядки та буфери (масиви). Ганебний тим, що є атакою зловмисного програмного забезпечення, переповнення буфера може маніпулювати програмою даними. Не спосіб роботи програм .NET, рядки та масиви розподіляються на купі GC і перевіряється індексація. Єдиний спосіб виділити простір у стеці за допомогою C # - це за допомогою небезпечного ключового слова stackalloc .

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

І останнє, але не менш важливе, програма .NET робить щось досить непродуктивне зі стеком. CLR буде здійснювати стек потоку. Це дороге слово, яке означає, що воно не просто резервує розмір стека, але також гарантує, що простір зарезервовано у файлі підключення операційної системи, щоб стек завжди можна було замінити, коли це необхідно. Невиконання - це фатальна помилка і безумовно припиняє програму. Це відбувається лише на машині з дуже малою оперативною пам’яттю, яка запускає надто багато процесів, така машина перетвориться на патоку, перш ніж програми почнуть вмирати. Можлива проблема 15+ років тому, а не сьогодні. Програмісти, які налаштовують свою програму, щоб діяти як гоночний автомобіль F1, використовують <disableCommitThreadStack>елемент у файлі .config.

Fwiw, Cutler не припиняли проектувати операційні системи. Це фото було зроблено, коли він працював на Azure.


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


3
wrt до вашого коментаря 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
Якщо поведінка за замовчуванням більше не виконує стек, цей файл розмітки
Джон Стівен

5

За замовчуванням розмір стека за замовчуванням задається лінкером, і його можна замінити розробниками, змінивши значення PE в час посилання або для окремого потоку, вказавши dwStackSizeпараметр CreateThreadфункції WinAPI.

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

Чому значення дорівнює 1 Мб для 32-бітних процесів і 4 Мб для 64-бітових процесів? Думаю, ви повинні запитати розробників, які розробляли Windows, або чекати, коли хтось із них відповість на ваше запитання.

Напевно, Марк Русинович це знає, і ви можете з ним зв’язатися . Можливо, ви можете знайти цю інформацію в його книгах Windows Internals раніше шостого видання, де описано менше інформації про стеки, а не його статті . А може, Реймонд Чен знає причини, оскільки пише цікаві речі про внутрішні системи 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.