почему размер стековой памяти так ограничен?

Когда вы выделяете память в куче, единственным ограничением является свободная оперативная память (или виртуальная память). Это составляет Гб памяти.

Так почему же размер стека настолько ограничен (около 1 Мб)? Какая техническая причина мешает вам создавать действительно большие объекты в стеке?

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


person undu    schedule 07.05.2012    source источник
comment
Почему было бы целесообразно создавать большие объекты в куче? (Цепочки вызовов обычно помещаются в стек.)   -  person Makoto    schedule 07.05.2012
comment
Я думаю, что настоящий ответ проще, чем описывается в большинстве ответов: потому что мы всегда это делали так, и до сих пор все было хорошо, так зачем же менять?   -  person Jerry Coffin    schedule 07.05.2012
comment
@JerryCoffin Вы читали какие-либо ответы, опубликованные на данный момент? В этом вопросе есть более глубокое понимание.   -  person user1202136    schedule 07.05.2012
comment
@ user1202136: Я прочитал их все, но люди предполагают, и я предполагаю, что многие из факторов, которые они цитируют, вероятно, даже не были учтены при принятии первоначальных решений по этому вопросу. Выражаясь словами, иногда сигара - это просто сигара.   -  person Jerry Coffin    schedule 07.05.2012
comment
@JerryCoffin: Правда, никто не смог назвать исходные причины, которые, скорее всего, связаны с какой-то устаревшей архитектурой. Тем не менее, люди приводили аргументы, почему слишком большой размер стека может быть вредным.   -  person user1202136    schedule 07.05.2012
comment
Самым большим преимуществом стека является его отличная локальность ссылок, все, что вы читаете или пишете, почти всегда будет присутствовать в кэше L1 и TLB. Размещение огромных массивов в стеке просто выбрасывает это преимущество из окна.   -  person Hans Passant    schedule 07.05.2012
comment
Насколько большим мы должны сделать стек по умолчанию? О, я не знаю, сколько потоков мы можем запустить? Он взрывается где-то за K. ОК, тогда мы назовем его 2K, у нас есть 2 гигабайта виртуальных, так как насчет 1 мегабайта? Ага, хорошо, а какой следующий выпуск?   -  person Martin James    schedule 07.05.2012
comment
Мне очень странно, что нет никакой другой причины, кроме как это нормально работает. Но, может быть, это все-таки хороший ответ, поскольку, похоже, никто не предлагает хорошо задокументированного ответа.   -  person undu    schedule 08.05.2012


Ответы (8)


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

Например, 32-разрядное приложение обычно имеет виртуальное адресное пространство размером 2 ГБ. Это означает, что если размер стека равен 2 МБ (по умолчанию в pthreads), то вы можете создать максимум 1024 потока. Это может быть мало для таких приложений, как веб-серверы. Увеличение размера стека, скажем, до 100 МБ (т. Е. Вы резервируете 100 МБ, но не обязательно сразу выделяете 100 МБ для стека), это ограничит количество потоков примерно до 20, что может быть ограничением даже для простых приложений с графическим интерфейсом.

Интересный вопрос, почему у нас все еще есть этот лимит на 64-битных платформах. Я не знаю ответа, но предполагаю, что люди уже привыкли к некоторым «лучшим методам работы с стеком»: будьте осторожны, выделяя огромные объекты в куче и, если необходимо, вручную увеличивайте размер стека. Поэтому никому не показалось полезным добавлять поддержку «огромного» стека на 64-битных платформах.

person user1202136    schedule 07.05.2012
comment
Многие 64-битные машины имеют только 48-битные адреса (предоставлено большое преимущество по сравнению с 32-битными, но все еще ограничено). Даже с дополнительным пространством вы должны беспокоиться о том, как выполняется резервирование в отношении таблиц страниц, то есть всегда есть накладные расходы, связанные с наличием большего пространства. Вероятно, так же дешево, если не дешевле, выделить новый сегмент (mmap) вместо резервирования огромных пространств стека для каждого потока. - person edA-qa mort-ora-y; 07.05.2012
comment
@ edA-qamort-ora-y: Этот ответ не говорит о распределении, он говорит о резервировании виртуальной памяти, что почти бесплатно и, конечно, много быстрее, чем mmap. - person Mooing Duck; 07.05.2012

Один аспект, о котором еще никто не упомянул:

Ограниченный размер стека - это механизм обнаружения и устранения ошибок.

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

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

person Andreas Grapentin    schedule 16.12.2014
comment
У вас может быть аналогичная проблема с выделенными объектами (поскольку один из способов заменить рекурсию - это обрабатывать стек вручную). Это ограничение вынуждает использовать другие способы (которые не обязательно безопаснее / проще / ..) (обратите внимание на количество замечаний о реализации (игрушечного) списка с std::unique_ptr для написания деструктора (без использования интеллектуального указателя)). - person Jarod42; 12.06.2020

Это просто размер по умолчанию. Если вам нужно больше, вы можете получить больше - чаще всего, указав компоновщику выделить дополнительное пространство стека.

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

Вы должны найти правильный баланс для своей программы.


Некоторые люди, например @BJovke, считают, что виртуальная память по сути бесплатна. Это правда, что вам не обязательно иметь физическую память, поддерживающую всю виртуальную память. Вы должны уметь хотя бы выдавать адреса виртуальной памяти.

Однако на типичном 32-битном ПК размер виртуальной памяти такой же, как и размер физической памяти, потому что у нас есть только 32 бита для любого адреса, виртуального или нет.

Поскольку все потоки в процессе используют одно и то же адресное пространство, они должны разделить его между собой. А после того, как операционная система приняла свое участие, для приложения остается "всего" 2-3 ГБ. И этот размер является пределом для как физической , так и виртуальной памяти, потому что просто больше нет адресов.

person Bo Persson    schedule 07.05.2012
comment
Самая большая проблема с потоками заключается в том, что вы не можете легко передавать объекты стека другим потокам. Либо поток-производитель должен синхронно ждать, пока поток-потребитель освободит объект, либо необходимо делать дорогостоящие и вызывающие конкуренцию глубокие копии. - person Martin James; 07.05.2012
comment
@MartinJames: Никто не говорит, что все объекты должны быть в стеке, мы обсуждаем, почему размер стека по умолчанию мал. - person Mooing Duck; 07.05.2012
comment
Пространство не будет потрачено зря, размер стека - это всего лишь резерв непрерывного виртуального адресного пространства. Таким образом, если вы установите размер стека в 100 МБ, объем оперативной памяти, который будет фактически использоваться, будет зависеть от потребления стека в потоках. - person BJovke; 13.04.2017
comment
@BJovke - Но виртуальное адресное пространство все равно будет израсходовано. В 32-битном процессе это ограничено несколькими ГБ, поэтому простое резервирование 20 * 100 МБ вызовет проблемы. - person Bo Persson; 13.04.2017

Во-первых, стек является непрерывным, поэтому, если вы выделяете 12 МБ, вы должны удалить 12 МБ, если хотите опуститься ниже того, что вы создали. Также становится труднее перемещать предметы. Вот пример из реальной жизни, который может упростить понимание:

Допустим, вы складываете коробки по комнате. Чем легче управлять:

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

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

person Scott Chamberlain    schedule 07.05.2012
comment
@MooingDuck Да, но вы работаете с виртуальной памятью в своей программе.Если я ввожу подпрограмму, помещаю что-то в стек, а затем возвращаюсь из подпрограммы, мне нужно будет либо освободить, либо переместить созданный мной объект, прежде чем я смогу раскрутиться стек, чтобы вернуться туда, откуда я пришел. - person Scott Chamberlain; 07.05.2012
comment
хотя мой комментарий был неправильно истолкован (и я удалил его), я все еще не согласен с этим ответом. Удаление 12 МБ из верхней части стека - это буквально один код операции. Это практически бесплатно. Также компиляторы могут и действительно обманывают правило стека, поэтому нет, им не нужно копировать / перемещать объект перед раскручиванием, чтобы вернуть его. Так что я думаю, что ваш комментарий тоже неверен. - person Mooing Duck; 07.05.2012
comment
Что ж, обычно не имеет большого значения, что для освобождения 12 МБ один код операции в стеке превышает 100 в куче - это, вероятно, ниже уровня шума фактической обработки буфера 12 МБ. Если компиляторы хотят обмануть, когда замечают, что возвращается смехотворно большой объект (например, перемещая SP перед вызовом, чтобы сделать объектное пространство частью стека вызывающих), тогда это нормально, TBH, разработчики, которые возвращают такие объекты (а не указатели / ссылки) несколько затрудняют программирование. - person Martin James; 07.05.2012
comment
@MartinJames: В спецификации C ++ также говорится, что функция обычно может помещать данные непосредственно в целевой буфер и не использовать временный, поэтому, если вы будете осторожны, нет накладных расходов на возврат 12-мегабайтного буфера по значению. - person Mooing Duck; 07.05.2012

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

Стек, конечно, живет в куче, но все же, поскольку он используется постоянно, он, вероятно, никогда не покидает кеш (ы) ЦП, что делает его быстрее, чем просто средний доступ к куче. Это причина поддерживать размер стека в разумных пределах; чтобы сохранить его в кэше как можно больше. Выделение больших объектов стека (возможно, автоматическое изменение размера стека при переполнении) противоречит этому принципу.

Так что это хорошая парадигма производительности, а не просто пережиток старых времен.

person Ruud van Gaal    schedule 17.03.2014
comment
Хотя я действительно считаю, что кеширование играет большую роль в причине искусственного уменьшения размера стека, я должен исправить вас в заявлении, что стек живет в куче. И стек, и куча находятся в памяти (виртуально или физически). - person Sebastian Hojas; 26.06.2014
comment
Как близко или далеко связано со скоростью доступа? - person Minh Nghĩa; 18.12.2019
comment
@ MinhNghĩa Ну, переменные в ОЗУ кэшируются в памяти L2, затем они кэшируются в памяти L1, и даже они кэшируются в регистрах. Доступ к ОЗУ осуществляется медленно, к L2 - быстрее, к L1 - еще быстрее, а к регистру - быстрее всего. Я думаю, что OP имел в виду, что переменные, хранящиеся в стеке, должны быть доступны быстро, поэтому ЦП будет изо всех сил стараться держать переменные стека рядом с ним, поэтому вы хотите сделать его маленьким, следовательно, ЦП может получить доступ к переменным быстрее. - person 157 239n; 24.09.2020

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

В «Алгоритмах» Седжвика есть пара хороших примеров «удаления» рекурсии из рекурсивных алгоритмов, таких как QuickSort, путем замены рекурсии на итерацию. На самом деле алгоритм все еще рекурсивный, и все еще есть стек, но вы размещаете стек сортировки в куче, а не используете стек времени выполнения.

(Я предпочитаю второе издание с алгоритмами, приведенными на Паскале. Его можно использовать за восемь долларов.)

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

person Mike Crawford    schedule 16.12.2014

Размещение больших объектов в стеке, скажем, 100 МБ сделало бы невозможным на большинстве машин их одновременную загрузку в кеш, что в значительной степени сводит на нет цель стека.

Смысл стека состоит в том, чтобы иметь небольшие объекты, принадлежащие к одной области (и, следовательно, обычно требуемые вместе или близко друг к другу), хранящиеся вместе в смежных адресах памяти, чтобы программа могла загружать их все в кэш по адресу в то же время, сводя к минимуму промахи в кэше и, в общем, время, которое ЦП должен ждать, пока он не получит недостающий фрагмент данных из более медленной ОЗУ.

Объект размером 50 МБ, хранящийся в стеке, не поместится в кеш, что означает, что после каждой строки кеша будет время ожидания ЦП до тех пор, пока из ОЗУ не будет доставлен следующий фрагмент данных, что означает засорение стека вызовов и не получение каких-либо значительных результатов. выгода (по скорости) по сравнению с загрузкой из кучи.

person mosegui    schedule 07.04.2021

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

person Martin James    schedule 07.05.2012
comment
Никто не говорит, что все объекты должны быть в стеке, мы обсуждаем, почему размер стека по умолчанию небольшой. - person Mooing Duck; 07.05.2012
comment
Это не мало! Сколько вызовов функций вам нужно выполнить, чтобы использовать 1 МБ стека? В любом случае значения по умолчанию легко изменить в компоновщике, поэтому остается вопрос: «Зачем использовать стек вместо кучи?» - person Martin James; 07.05.2012
comment
один вызов функции. int main() { char buffer[1048576]; } Это очень распространенная проблема новичков. Конечно, есть простой обходной путь, но почему мы должны обходить размер стека? - person Mooing Duck; 07.05.2012
comment
Ну, с одной стороны, я не хотел бы, чтобы требования стека в 12 МБ (или даже 1 МБ) накладывались на стек каждого потока, вызывающего пораженную функцию. Тем не менее, я должен согласиться, что 1 МБ немного скупо. Я был бы счастлив со 100 МБ по умолчанию, в конце концов, ничто не мешает мне уменьшить его до 128 КБ точно так же, как ничто не мешает другим разработчикам увеличить его. - person Martin James; 07.05.2012
comment
Почему бы вам не добавить в поток 12 МБ стека? Единственная причина в том, что стопки маленькие. Это рекурсивный аргумент. - person Mooing Duck; 07.05.2012
comment
Стеки не маленькие. Максимальный размер 128 КБ отлично работает в моих приложениях - это уже мало. 1 МБ - это слишком много для большинства приложений, но это имеет значение только для приложений, которые запускают ~ 2K потоков. Я никогда не чувствовал ни малейшего подозрения, что можно выделить хотя бы 100 КБ стека для чего-либо. В любом случае я ответил на вопрос OP - я не знаю никаких технических причин ограничивать пространство стека до 1 МБ. Это даже не жесткое ограничение - оно настраивается для каждого приложения. - person Martin James; 07.05.2012