스택 메모리 크기가 그렇게 제한되는 이유는 무엇입니까?


81

힙에 메모리를 할당 할 때 유일한 제한은 여유 RAM (또는 가상 메모리)입니다. 그것은 Gb의 메모리를 만듭니다.

그렇다면 스택 크기가 왜 그렇게 제한되어 있습니까 (약 1Mb)? 스택에 정말 큰 개체를 만들지 못하게하는 기술적 이유는 무엇입니까?

업데이트 : 내 의도 힘이 명확하지 않을, 내가 원하지 않는 스택에 큰 개체를 할당하고, 내가 필요하지 않습니다 더 큰 스택을. 이 질문은 순수한 호기심입니다.


힙에 큰 개체를 만드는 것이 실용적인 이유는 무엇입니까? (콜 체인은 일반적으로 스택에 있습니다.)
Makoto

4
진짜 대답은 대부분의 대답이 묘사하는 것보다 더 간단하다고 생각합니다. "왜냐하면 우리가 항상 해왔 기 때문에 지금까지 괜찮 았기 때문입니다. 왜 변화합니까?"
Jerry Coffin

@JerryCoffin 지금까지 게시 된 답변을 읽었습니까? 이 질문에 대한 더 많은 통찰력이 있습니다.
user1202136

3
@ user1202136 : 나는 그것들을 모두 읽었지만 사람들은 추측하고 있으며 내 추측은 그들이 인용하는 많은 요소가 아마도 주제에 대한 원래 결정을 내리는 데 고려되지 않았을 것입니다. "때때로 시가는 시가 일뿐입니다."
Jerry Coffin

3
"기본 스택을 얼마나 크게 만들어야합니까?" "오, 몰라요, 얼마나 많은 스레드를 실행할 수 있습니까?" "K 이상 어딘가에서 날아간다" "좋아, 그럼 2K라고 부를 게. 2 기가의 가상이 있으니 1 메가는 어때?" "예, 좋습니다. 다음 호는 무엇입니까?"
Martin James

답변:


45

내 직감은 다음과 같습니다. 스택은 힙만큼 관리하기 쉽지 않습니다. 스택은 연속적인 메모리 위치에 저장되어야합니다. 즉, 필요에 따라 스택을 무작위로 할당 할 수 없지만 최소한 해당 목적을 위해 가상 주소를 예약해야합니다. 예약 된 가상 주소 공간의 크기가 클수록 만들 수있는 스레드 수가 줄어 듭니다.

예를 들어, 32 비트 애플리케이션은 일반적으로 2GB의 가상 주소 공간을 갖습니다. 즉, 스택 크기가 2MB (pthreads의 기본값)이면 최대 1024 개의 스레드를 만들 수 있습니다. 이것은 웹 서버와 같은 애플리케이션의 경우 작을 수 있습니다. 예를 들어 스택 크기를 100MB로 늘리면 (즉, 100MB를 예약하지만 반드시 스택에 100MB를 즉시 할당 할 필요는 없음) 스레드 수가 약 20 개로 제한되어 단순한 GUI 응용 프로그램에서도 제한 될 수 있습니다.

흥미로운 질문은 왜 우리가 64 비트 플랫폼에서 여전히 이러한 제한을 가지고 있는지입니다. 답은 모르겠지만 사람들이 이미 몇 가지 "스택 모범 사례"에 익숙하다고 가정합니다. 힙에 거대한 개체를 할당하도록주의하고 필요한 경우 스택 크기를 수동으로 늘립니다. 따라서 아무도 64 비트 플랫폼에서 "대용량"스택 지원을 추가하는 것이 유용하지 않다고 생각했습니다.


많은 64 비트 컴퓨터에는 48 비트 주소 만 있습니다 (32 비트에 비해 큰 이득이 있지만 여전히 제한됨). 추가 공간이 있더라도 페이지 테이블에 대한 예약 방법에 대해 걱정해야합니다. 즉, 더 많은 공간을 확보하는 데 항상 오버 헤드가 발생합니다. 각 스레드에 대해 거대한 스택 공간을 예약하는 대신 새 세그먼트 (mmap)를 할당하는 것이 더 저렴하지는 않지만 저렴할 것입니다.
edA-qa mort-ora-y

4
@ edA-qamort-ora-y :이 답변은 할당에 대한 것이 아니라 가상 메모리 예약 에 대해 이야기하고 있습니다. 가상 메모리 예약 은 거의 무료이며 mmap보다 확실히 훨씬 빠릅니다.
Mooing Duck

33

아직 아무도 언급하지 않은 한 가지 측면 :

제한된 스택 크기는 오류 감지 및 억제 메커니즘입니다.

일반적으로 C 및 C ++에서 스택의 주요 작업은 호출 스택 및 로컬 변수를 추적하는 것이며 스택이 범위를 벗어나면 거의 항상 설계 및 / 또는 응용 프로그램 동작의 오류입니다. .

스택이 임의로 커질 수있는 경우 이러한 오류 (예 : 무한 재귀)는 운영 체제 리소스가 고갈 된 후에 만 ​​매우 늦게 포착됩니다. 이는 스택 크기에 임의의 제한을 설정함으로써 방지됩니다. 실제 크기는 시스템 성능 저하를 방지 할 수있을만큼 작다는 점을 제외하면 그다지 중요하지 않습니다.


할당 된 객체와 비슷한 문제가있을 수 있습니다 (재귀를 대체하는 방법은 스택을 수동으로 처리하는 것입니다). 이 제한은 다른 방법을 사용하도록 강요합니다 (더 안전하고 간단하게 / .. 필요하지 않음) ( std::unique_ptr소멸자를 작성하기 위해 (장난감) 목록 구현에 대한 언급의 수에 유의하십시오 ( 스마트 포인터에 의존하지 않음)).
Jarod42

15

기본 크기입니다. 더 많이 필요하면 링커에 추가 스택 공간을 할당하도록 지시하여 더 많이 얻을 수 있습니다.

큰 스택의 단점은 스레드를 여러 개 만들면 각각 하나의 스택이 필요하다는 것입니다. 모든 스택이 다중 MB를 할당하지만 사용하지 않는 경우 공간이 낭비됩니다.

프로그램에 적합한 균형을 찾아야합니다.


@BJovke와 같은 일부 사람들은 가상 메모리가 본질적으로 무료라고 믿습니다. 모든 가상 메모리를 지원하는 물리적 메모리가 필요하지 않다는 것은 사실입니다. 적어도 가상 메모리에 주소를 제공 할 수 있어야합니다.

그러나 일반적인 32 비트 PC에서 가상 메모리의 크기는 실제 메모리의 크기와 동일합니다. 가상이든 아니든 모든 주소에 대해 32 비트 만 있기 때문입니다.

프로세스의 모든 스레드가 동일한 주소 공간을 공유하기 때문에 스레드간에 분할해야합니다. 그리고 운영 체제가 그 역할을 맡은 후에는 응용 프로그램에 "만"2-3GB가 남습니다. 그리고 그 크기는 더 이상 주소가 없기 때문에 물리적 메모리 가상 메모리 모두에 대한 제한입니다 .


가장 큰 스레딩 문제는 스택 객체를 다른 스레드에 쉽게 신호 할 수 없다는 것입니다. 생산자 스레드는 소비자 스레드가 개체를 해제 할 때까지 동기식으로 기다려야하거나 비용이 많이 들고 경합을 발생시키는 깊은 복사본을 만들어야합니다.
Martin James

2
@MartinJames : 아무도 모든 객체가 스택에 있어야한다고 말하지 않습니다. 우리는 기본 스택 크기가 왜 작은 지에 대해 논의하고 있습니다.
Mooing Duck

공간은 낭비되지 않으며 스택 크기는 연속 가상 주소 공간의 예약일뿐입니다. 따라서 스택 크기를 100MB로 설정하면 실제로 사용될 RAM 양은 스레드의 스택 사용량에 따라 달라집니다.
BJovke

1
@BJovke-그러나 가상 주소 공간은 여전히 ​​사용됩니다. 32 비트 프로세스에서는 몇 GB로 제한되므로 20 * 100MB 만 예약하면 문제가 발생합니다.
Bo Persson 2017

7

우선 스택은 연속적이므로 12MB를 할당하면 생성 한 항목보다 아래로 이동하려면 12MB를 제거해야합니다. 또한 움직이는 물체가 훨씬 더 어려워집니다. 다음은 이해하기 쉽게 만드는 실제 사례입니다.

방 주위에 상자를 쌓고 있다고 가정 해 보겠습니다. 관리가 더 쉬운 방법 :

  • 무게에 관계없이 상자를 쌓아 두지 만 바닥에 무언가를 가져와야 할 때는 전체 더미를 되돌려 야합니다. 더미에서 아이템을 꺼내 다른 사람에게 주려면 모든 상자를 떼어 내고 상자를 다른 사람의 더미로 옮겨야합니다 (스택 만 해당).
  • 모든 상자 (정말 작은 상자 제외)를 다른 물건 위에 쌓아 두지 않는 특수 영역에 놓고 종이 (포인터) 위에 놓은 위치를 적어 놓고 종이를 위에 놓습니다. 더미. 상자를 다른 사람에게 줄 필요가있는 경우 더미에서 종이 한 장을 건네 주거나 종이 사본을주고 원본은 더미에 있던 곳에 두십시오. (스택 + 힙)

이 두 가지 예는 전체적인 일반화이며 비유에서 노골적으로 잘못된 몇 가지 점이 있지만 두 경우 모두에서 이점을 보는 데 도움이되기를 바랍니다.


@MooingDuck 예,하지만 프로그램의 가상 메모리에서 작업하고 있습니다. 서브 루틴에 들어가서 스택에 무언가를 넣은 다음 서브 루틴에서 돌아 오면 풀기 전에 생성 한 객체를 할당 해제하거나 이동해야합니다. 내가 온 곳으로 돌아 가기위한 스택.
Scott Chamberlain

1
내 의견은 오해로 인해 삭제되었지만 여전히이 답변에 동의하지 않습니다. 스택 상단에서 12MB를 제거하는 것은 말 그대로 하나의 opcode입니다. 기본적으로 무료입니다. 또한 컴파일러는 "스택"규칙을 속일 수 있으며이를 수행하므로 반환하기 위해 해제하기 전에 객체를 복사 / 이동할 필요가 없습니다. 그래서 당신의 의견도 틀렸다고 생각합니다.
Mooing Duck

글쎄, 일반적으로 12MB를 할당 해제하면 힙에서 100 개가 넘는 스택에서 하나의 opcode가 필요하다는 것은 그다지 중요하지 않습니다. 실제로 12MB 버퍼를 처리하는 노이즈 수준보다 낮을 것입니다. 컴파일러가 엄청나게 큰 개체가 반환되는 것을 알아 차렸을 때 (예 : 호출자 스택의 개체 공간 부분을 만들기 위해 호출 전에 SP를 이동하여) 속임수를 쓰려고한다면, TBH를 반환하는 개발자는 괜찮습니다. (포인터 / 참조가 아닌) 객체는 프로그래밍에 다소 어려움이 있습니다.
Martin James

@MartinJames : C ++ 사양에서는 함수가 일반적으로 데이터를 대상 버퍼에 직접 넣을 수 있고 임시 버퍼를 사용하지 않을 수 있으므로주의하면 값별로 12MB 버퍼를 반환하는 데 오버 헤드가 없습니다.
Mooing Duck

3

근거리에서 원거리 순으로 스택을 생각하십시오. 레지스터는 CPU에 가깝고 (빠름) 스택은 조금 더 멀고 (하지만 상대적으로 가까움) 힙은 멀리 떨어져 있습니다 (느린 액세스).

스택은 물론 힙에 있지만 계속 사용되기 때문에 CPU 캐시를 떠나지 않아 평균 힙 액세스보다 빠릅니다. 이것이 스택 크기를 합리적으로 유지해야하는 이유입니다. 가능한 한 많이 캐시 된 상태로 유지합니다. 큰 스택 객체 할당 (오버플로가 발생하면 스택 크기 자동 조정)은이 원칙에 위배됩니다.

따라서 예전에서 남은 것이 아니라 성능에 대한 좋은 패러다임입니다.


4
캐싱이 스택 크기를 인위적으로 줄이는 데 큰 역할을한다고 생각하지만 "스택이 힙에 존재한다"는 말을 수정해야합니다. 스택과 힙은 모두 메모리 (가상 또는 물리적)에 있습니다.
Sebastian Hojas 2014-06-26

"근거리 또는 원거리"는 액세스 속도와 어떻게 관련됩니까?
호치민 NGHIA

@ MinhNghĩa 음, RAM의 변수는 L2 메모리에 캐시 된 다음 L1 메모리에 캐시 된 다음 레지스터에도 캐시됩니다. RAM에 대한 액세스는 느리고 L2에 대한 액세스는 더 빠르며 L1은 여전히 ​​더 빠르며 레지스터는 가장 빠릅니다. OP가 의미하는 바는 스택에 저장된 변수가 빠르게 액세스되어야하기 때문에 CPU는 스택 변수를 가까이에 유지하기 위해 최선을 다할 것이므로 작게 만들고 싶으므로 CPU가 변수에 더 빨리 액세스 할 수 있습니다.
157239n

1

큰 스택이 필요하다고 생각하는 많은 작업은 다른 방법으로 수행 할 수 있습니다.

Sedgewick의 "Algorithms"에는 재귀를 반복으로 대체하여 QuickSort와 같은 재귀 알고리즘에서 재귀를 "제거"하는 몇 가지 좋은 예가 있습니다. 실제로 알고리즘은 여전히 ​​재귀 적이며 여전히 스택으로 존재하지만 런타임 스택을 사용하는 대신 힙에 정렬 ​​스택을 할당합니다.

(파스칼로 제공된 알고리즘을 사용하는 두 번째 버전을 선호합니다. 8 달러에 사용할 수 있습니다.)

그것을 보는 또 다른 방법은 큰 스택이 필요하다고 생각하면 코드가 비효율적이라는 것입니다. 더 적은 스택을 사용하는 더 좋은 방법이 있습니다.


-8

기술적 인 이유가 없다고 생각하지만 스택에 하나의 거대한 슈퍼 오브젝트 만 생성 한 이상한 앱일 것입니다. 스택 객체는 크기가 증가함에 따라 더 문제가되는 유연성이 부족합니다. 객체를 파괴하지 않고는 반환 할 수 없으며 다른 스레드에 대기열에 넣을 수 없습니다.


1
아무도 모든 객체가 스택에 있어야한다고 말하지 않습니다. 우리는 기본 스택 크기가 작은 이유에 대해 논의하고 있습니다.
Mooing Duck

작지 않다! 1MB의 스택을 사용하려면 얼마나 많은 함수 호출을 거쳐야합니까? 기본값은 어쨌든 링커에서 쉽게 변경되므로 '왜 힙 대신 스택을 사용합니까?'
Martin James

3
하나의 함수 호출. int main() { char buffer[1048576]; } 매우 흔한 초보자 문제입니다. 물론 쉬운 해결 방법이 있지만 스택 크기를 해결해야하는 이유는 무엇입니까?
Mooing Duck

글쎄요, 한 가지로, 저는 영향을받는 함수를 호출하는 모든 스레드의 스택에 12MB (또는 실제로 1MB)의 스택 요구 사항이 적용되는 것을 원하지 않습니다. 즉, 1MB가 약간 인색하다는 데 동의해야합니다. 나는 기본 100MB에 만족할 것이다. 결국 다른 개발자가 그것을 올리는 것을 막지 않는 것과 같은 방식으로 내가 그것을 128K로 낮추는 것을 막을 수있는 것은 없다.
Martin James

1
스레드에 12MB의 스택을 부여하고 싶지 않은 이유는 무엇입니까? 그 이유는 스택이 작기 때문입니다. 그것은 재귀 적 주장입니다.
Mooing Duck
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.