일반적으로 시스템이 스택을 없애고 메모리 관리에 힙을 사용하는 것이 더 효율적일 수 있습니까?


14

스택으로 수행 할 수있는 모든 것은 힙으로 수행 할 수 있지만 힙으로 수행 할 수있는 모든 것은 스택으로 수행 할 수있는 것은 아닙니다. 그 맞습니까? 그런 다음 단순성을 위해 특정 워크로드에서 약간의 성능을 잃어버린 경우에도 하나의 표준 (즉, 힙)을 사용하는 것이 더 좋지 않을 수 있습니까?

모듈 성과 성능의 균형을 생각하십시오. 이것이이 시나리오를 설명하는 가장 좋은 방법은 아니지만 일반적으로 이해와 디자인의 단순성이 더 나은 성능의 가능성이 있더라도 더 나은 옵션이 될 수 있습니다.


1
C 및 C ++에서는 힙에 할당 된 메모리를 명시 적으로 할당 해제해야합니다. 더 간단하지 않습니다.
user16764

나는 C # 구현을 사용하여 스택 객체가 끔찍한 가비지 수집과 함께 힙과 같은 영역에 할당되었다는 것을 밝혀 냈습니다. 내 솔루션? 이동 모든 지속적인 힙 메모리에 가능한 (예를 들어 루프 변수, 임시 변수 등). 프로그램이 RAM의 10 배를 먹고 10 배의 속도로 실행되도록했습니다.
imallett

@IanMallett : 문제 및 해결책에 대한 설명을 이해하지 못합니다. 어딘가에 더 많은 정보가있는 링크가 있습니까? 일반적으로 스택 기반 할당이 더 빠릅니다.
Frank Hileman

@ FrankHileman의 기본 문제는 다음과 같습니다. 사용중인 C #의 구현은 가비지 수집 속도가 매우 떨어졌습니다. "솔루션"은 모든 변수를 영구적으로 만들어 런타임에 메모리 작업이 발생하지 않도록하는 것입니다. 필자는 C # / XNA 개발에 대한 의견을 작성 하여 컨텍스트에 대해 논의했습니다.
imallett

@IanMallett : 감사합니다. 요즘 C #을 주로 사용하는 전 C / C ++ 개발자로서 저의 경험은 상당히 다릅니다. 도서관이 가장 큰 문제라는 것을 알았습니다. XBox360 플랫폼이 .net 개발자들에게는 반쯤 구운 것 같습니다. 일반적으로 GC 문제가 발생하면 풀링으로 전환합니다. 도움이됩니다.
Frank Hileman 2018 년

답변:


30

힙은 빠른 메모리 할당 및 할당 해제에 좋지 않습니다. 제한된 기간 동안 많은 양의 메모리를 확보하려는 경우 힙이 최선의 선택이 아닙니다. 매우 간단한 할당 / 할당 해제 알고리즘을 갖춘 스택은 자연스럽게이 기능을 능가합니다 (하드웨어에 내장되어있는 경우에도 더 그렇습니다). 따라서 사람들은 인수를 함수에 전달하고 로컬 변수를 저장하는 등의 용도로 가장 많이 사용합니다 중요한 단점은 공간이 제한되어 있기 때문에 큰 물체를 보관하거나 오래 사용하는 물체에 사용하는 것은 나쁜 생각입니다.

프로그래밍 언어를 단순화하기 위해 스택을 완전히 제거하는 것은 IMO의 잘못된 방법입니다. 더 나은 접근 방식은 차이점을 추상화하고 컴파일러가 사용할 스토리지 종류를 파악하도록하는 것입니다. 인간이 생각하는 방식에 더 가까운 수준의 구성-실제로 C #, Java, Python 등과 같은 고급 언어는이를 정확하게 수행합니다. 힙 할당 객체와 스택 할당 프리미티브 (.NET 링고의 '참조 유형'과 '값 유형')에 대해 거의 동일한 구문을 제공하며, 완전히 투명하거나 언어를 사용하기 위해 이해해야 할 몇 가지 기능적 차이점이 있습니다. 올바르게 (그러나 실제로 스택과 힙이 내부에서 어떻게 작동하는지 알 필요는 없습니다).


2
이것이 좋은 방법 :) 초보자에게 정말 간결하고 유익한 정보!
Dark Templar

1
많은 CPU에서 스택은 하드웨어에서 처리되는데, 이는 언어 이외의 문제이지만 런타임에 큰 역할을합니다.
Patrick Hughes

@Patrick Hughes : 예. 그러나 힙도 하드웨어에도 있습니다. 그렇지 않습니까?
Dark Templar

@Dark Patrick이 말하고 싶은 것은 x86과 같은 아키텍처에는 스택을 관리하기위한 특수 레지스터와 스택에 무언가를 넣거나 제거하기위한 특수 명령어가 있다는 것입니다. 그것은 꽤 빠릅니다.
FUZxxl

3
@Donal Fellows : 모두 사실입니다. 그러나 요점은 스택과 힙 모두 강점과 약점을 가지며, 따라서이를 사용하면 가장 효율적인 코드가 생성된다는 것입니다.
tdammers

8

간단히 말해서, 스택은 약간의 성능이 아닙니다. 힙보다 수백 또는 수천 배 빠릅니다. 또한 대부분의 최신 컴퓨터에는 스택에 대한 하드웨어 지원 (x86과 같은)이 있으며 호출 스택과 같은 하드웨어 기능은 제거 할 수 없습니다.


최신 머신이 스택에 대해 하드웨어를 지원한다고 말하는 것은 무엇을 의미합니까? 스택 자체가 이미 하드웨어에 있습니까?
Dark Templar

1
x86에는 스택을 처리하기위한 특수 레지스터와 명령어가 있습니다. x86은 힙을 지원하지 않습니다. 이러한 것들이 OS에 의해 생성됩니다.
Pubby

8

아니

C ++의 스택 영역은 엄청나게 빠릅니다. 경험이 많은 C ++ 개발자가 해당 기능을 사용하지 않을 가능성이 없습니다.

C ++에서는 선택의 여지 가 있으며 제어 할 수 있습니다. 설계자들은 특히 상당한 실행 시간이나 공간을 추가 한 기능을 도입하려는 경향이 없었습니다.

그 선택을 행사

모든 객체를 동적으로 할당해야하는 라이브러리 또는 프로그램을 빌드하려는 경우 C ++로 수행 할 수 있습니다. 상대적으로 느리게 실행되지만 '모듈성'을 가질 수 있습니다. 우리 중 나머지 사람들에게는 모듈화는 항상 선택 사항입니다. 둘 다 우수 / 빠른 구현에 필요하므로 필요에 따라 도입하십시오.

대안

각 객체에 대한 스토리지를 힙에 작성해야하는 다른 언어가 있습니다. 상당히 느리므로 두 가지를 모두 배우는 것 (IMO)을 배우는 것보다 나쁜 방식으로 디자인 (실제 프로그램)을 손상시킵니다.

둘 다 중요하며 C ++은 주어진 각 시나리오에 대해 효과적으로 전력 사용을 제공합니다. 그러나 C ++ 언어는 OP의 이러한 요소가 중요한 경우 (예 : 고급 언어로 읽기) 설계에 적합하지 않을 수 있습니다.


힙은 실제로 스택과 동일한 속도이지만 할당을위한 특수한 하드웨어 지원은 없습니다. 반면에, 힙을 많이 가속화하는 방법이 있습니다 (전문적인 기술을 만드는 여러 조건에 따라 다름).
Donal Fellows

@DonalFellows : 스택에 대한 하드웨어 지원은 관련이 없습니다. 중요한 것은 어떤 것이 릴리스 될 때마다 할당 된 것을 릴리스 할 수 있다는 것을 아는 것입니다. 일부 프로그래밍 언어에는 개체를 독립적으로 해제 할 수있는 힙이 없지만 "다음에 할당 된 모든 항목을 해제"방법 만 있습니다.
supercat

6

그런 다음 단순성을 위해 특정 워크로드에서 약간의 성능을 잃어버린 경우에도 하나의 표준 (즉, 힙)을 사용하는 것이 더 좋지 않을 수 있습니까?

실제로 성능이 크게 향상 될 수 있습니다!

다른 사람들이 지적했듯이 스택은 LIFO (마지막) 규칙을 따르는 데이터를 관리하기위한 매우 효율적인 구조입니다. 스택의 메모리 할당 / 해제는 일반적으로 CPU의 레지스터를 변경하는 것입니다. 레지스터 변경은 거의 항상 프로세서가 수행 할 수있는 가장 빠른 작업 중 하나입니다.

힙은 일반적으로 상당히 복잡한 데이터 구조이며 메모리 할당 / 해제는 관련된 모든 부기 작업을 수행하기 위해 많은 지시를받습니다. 더 나쁜 것은 일반적인 구현에서 힙으로 작업하기위한 모든 호출은 운영 체제를 호출 할 가능성이 있다는 것입니다. 운영 체제 호출은 시간이 많이 걸립니다! 프로그램은 일반적으로 사용자 모드에서 커널 모드로 전환해야하며, 이러한 상황이 발생할 때마다 운영 체제는 다른 프로그램에 더 많은 긴급한 요구가 있고 프로그램이 대기해야한다고 결정할 수 있습니다.


5

시뮬라는 모든 것을 위해 힙을 사용했습니다. 힙에 모든 것을 넣으면 항상 지역 변수에 대한 간접적 인 수준이 유도되고 가비지 콜렉터에 추가 압력이 가해집니다 (가비지 콜렉터가 실제로 빨려 들어간 것을 고려해야합니다 ). 이것이 바로 Bjarne이 C ++을 발명 한 이유입니다.


기본적으로 C ++은 힙만 사용합니까?
Dark Templar

2
@Dark : 무엇? 아닙니다. Simula에 스택이 부족하다는 것이 더 나은 영감을주었습니다.
fredoverflow 2016 년

아 지금 무슨 말인지 알 겠어! Thanks +1 :)
Dark Templar

3

스택은 함수 호출과 관련된 메타 데이터와 같은 LIFO 데이터에 매우 효율적입니다. 이 스택은 또한 CPU의 고유 한 디자인 기능을 활용합니다. 이 수준의 성능은 프로세스의 다른 모든 것의 기본 요소이므로 해당 수준에서 "작은"적중을 가하면 매우 광범위하게 전파됩니다. 또한 힙 메모리는 OS에 의해 움직일 수 있으며 스택에 치명적입니다. 스택은 힙으로 구현 될 수 있지만 가장 세부적인 수준에서 프로세스의 모든 부분에 영향을 미치는 오버 헤드가 필요합니다.


2

코드 작성 측면에서 "효율적"일 수도 있지만 소프트웨어 효율성 측면에서는 그렇지 않습니다. 스택 할당은 기본적으로 무료입니다 (로컬 변수를 위해 스택에서 스택 포인터를 이동하고 공간을 예약하는 데 약간의 기계 명령어 만 필요함).

스택 할당에는 거의 시간이 걸리지 않으므로 매우 효율적인 힙에 대한 할당도 100k (1M +가 아닌 경우)의 시간이 느려집니다.

이제 일반적인 애플리케이션이 사용하는 로컬 변수 및 기타 데이터 구조의 수를 상상해보십시오. 루프 카운터로 사용하는 모든 작은 "i"는 백만 배 더 느리게 할당됩니다.

하드웨어가 충분히 빠르면 힙만 사용하는 응용 프로그램을 작성할 수 있습니다. 그러나 이제는 힙을 활용하고 동일한 하드웨어를 사용하는 경우 어떤 종류의 응용 프로그램을 작성할 수 있는지 이미징합니다.


"일반 애플리케이션이 사용하는 로컬 변수 및 기타 데이터 구조의 수를 상상하십시오"라고 말할 때 다른 데이터 구조에 대해 구체적으로 언급하고있는 것은 무엇입니까?
암흑 기사단

1
"100k"와 "1M +"값은 어떻게 과학적인가? 아니면 "많이"라고 말하는 방법일까요?
Bruno Reis

@Bruno-IMHO 100K 및 1M 숫자는 실제로 포인트를 증명하기 위해 보수적 인 추정치입니다. VS 및 C ++에 익숙한 경우 스택에 100 바이트를 할당하는 프로그램을 작성하고 힙에 100 바이트를 할당하는 프로그램을 작성하십시오. 그런 다음 디스 어셈블리 뷰로 전환하고 각 할당에 필요한 어셈블리 명령 수를 세어보십시오. 힙 작업은 일반적으로 Windows DLL에 대한 여러 함수 호출이며 버킷과 링크 된 목록이 있으며 통합 및 기타 알고리즘이 있습니다. 스택을 사용하면 하나의 어셈블리 명령어 "add esp, 100"로
요약

2
"100k (1M +가 아닌 경우)의 속도가 느리다"? 상당히 과장된 것입니다. 2 배 정도 느리게, 아마도 3 배로하자. 적어도, 리눅스는 코어 i5에서 6 초 이내에 100M 힙 할당 (+ 일부 주변 명령)을 수행 할 수 있습니다. 할당 당 수백 개의 명령을 초과 할 수는 없습니다. 실제로는 거의 확실하지 않습니다. 그것의 경우 여섯 개 스택보다 느린 크기 순서를 OS의 힙 구현 majorly 뭔가 문제가있다. 물론 Windows에는 많은 문제가 있지만 그 점은 ...
leftaroundabout

1
중재자가이 전체 주석 스레드를 종료하려고합니다. 그래서 여기에 거래가 있습니다, 나는 실제 숫자가 나의 것에서 뽑 혔음을 인정합니다. 그러나, 그 요소가 실제로, 정말로 크고 더 많은 주석을
달지

2

"가비지 수집은 빠르지 만 스택은 빠름"에 관심이있을 수 있습니다.

http://dspace.mit.edu/bitstream/handle/1721.1/6622/AIM-1462.ps.Z

올바르게 읽으면이 사람들은 C 컴파일러를 수정하여 힙에 "스택 프레임"을 할당 한 다음 가비지 수집을 사용하여 스택을 팝하는 대신 프레임을 할당 해제합니다.

스택 할당 된 "스택 프레임"은 힙 할당 된 "스택 프레임"보다 성능이 뛰어납니다.


1

콜 스택은 어떻게 힙에서 작동합니까? 기본적으로 모든 프로그램에서 힙에 스택을 할당해야하므로 OS + 하드웨어에서이를 수행하지 않는 이유는 무엇입니까?

정말 간단하고 효율적으로 작업하려면 사용자에게 메모리를 할당하고 처리하도록하십시오. 물론 아무도 자신의 모든 것을 구현하고 싶지 않기 때문에 스택과 힙이 있습니다.


엄밀히 말하면 "호출 스택"은 프로그래밍 언어 런타임 환경의 필수 기능이 아닙니다. 예를 들어, 그래프 축소 (내가 코딩 한)에 의해 지연 평가 된 기능 언어를 간단하게 구현하면 호출 스택이 없습니다. 그러나 콜 스택은 매우 널리 유용하고 널리 사용되는 기술입니다. 특히 최신 프로세서가 사용한다고 가정하고 사용에 최적화되어 있기 때문입니다.
Ben

@Ben-언어에서 메모리 할당과 같은 것을 추상화하는 것은 사실이지만 좋은 일이지만 현재 널리 사용되는 컴퓨터 아키텍처는 변경하지 않습니다. 따라서 그래프 축소 코드는 스택을 실행할 때와 같이 스택을 계속 사용합니다.
Ingo

@Ingo 실제로 의미있는 의미는 아닙니다. 물론, OS는 전통적으로 "스택"이라고하는 메모리 섹션을 초기화하며이를 가리키는 레지스터가있을 것입니다. 그러나 소스 언어의 함수는 콜 순서에서 스택 프레임으로 표시되지 않습니다. 함수 실행은 전적으로 힙에서 데이터 구조를 조작하여 표현됩니다. 마지막 호출 최적화를 사용하지 않아도 "스택 오버플로"는 불가능합니다. 이것이 바로 "콜 스택"에 대한 기본 사항이 없다고 말할 때의 의미입니다.
Ben

나는 원어의 기능에 대해 말하지 않고 실제로 그래프 축소를 수행하는 인터프리터 (또는 기타)의 기능에 대해 이야기합니다. 그것들은 스택이 필요합니다. 현대 하드웨어는 그래프 축소를 수행하지 않기 때문에 이것은 분명합니다. 따라서 그래프 축소 알고리즘은 궁극적으로 기계 ode에 매핑되며 그들 사이에 서브 루틴 호출이있을 것입니다. QED.
Ingo

1

스택과 힙이 모두 필요합니다. 예를 들어 다음과 같은 다양한 상황에서 사용됩니다.

  1. 힙 할당에는 sizeof (a [0]) == sizeof (a [1])
  2. 스택 할당에는 sizeof (a)가 컴파일 타임 상수라는 제한이 있습니다.
  3. 힙 할당은 루프, 그래프 등 복잡한 데이터 구조를 수행 할 수 있습니다
  4. 스택 할당은 컴파일 타임 크기의 트리를 수행 할 수 있습니다
  5. 힙에는 소유권 추적이 필요합니다
  6. 스택 할당 및 할당 해제가 자동
  7. 포인터를 통해 힙 메모리를 한 스코프에서 다른 스코프로 쉽게 전달할 수 있습니다.
  8. 스택 메모리는 각 함수에 대해 로컬이며 개체 수명을 연장하려면 개체를 상위 범위로 이동해야합니다 (또는 멤버 함수 대신 개체 안에 저장).
  9. 힙은 성능이 좋지 않습니다.
  10. 스택은 매우 빠릅니다
  11. 힙 객체는 소유권을 갖는 포인터를 통해 함수에서 반환됩니다. 또는 shared_ptrs.
  12. 스택 객체는 소유권을 가지지 않는 참조를 통해 함수에서 반환됩니다.
  13. 힙은 올바른 종류의 삭제 또는 삭제 []로 모든 새 항목을 일치시켜야합니다.
  14. 스택 객체는 RAII 및 생성자 초기화 목록을 사용합니다.
  15. 힙 객체는 함수 내에서 언제든지 초기화 할 수 있으며 생성자 매개 변수를 사용할 수 없습니다
  16. 스택 객체는 초기화를 위해 생성자 매개 변수를 사용합니다
  17. 힙은 배열을 사용하며 런타임에 배열 크기가 변경 될 수 있음
  18. 스택은 단일 객체 용이며 컴파일 타임에 크기가 고정됩니다.

많은 세부 사항이 다르기 때문에 기본적으로 메커니즘을 전혀 비교할 수 없습니다. 그들에게 공통적 인 것은 둘 다 어떻게 든 메모리를 처리한다는 것입니다.


1

최신 컴퓨터에는 크고 느리고 주 메모리 시스템 외에 여러 계층의 캐시 메모리가 있습니다. 메인 메모리 시스템에서 1 바이트를 읽거나 쓰는 데 필요한 시간 내에 가장 빠른 캐시 메모리에 수십 번 액세스 할 수 있습니다. 따라서 한 번의 위치에 천 번 액세스하는 것이 각각 한 번에 1,000 (또는 100) 독립 위치에 액세스하는 것보다 훨씬 빠릅니다. 대부분의 응용 프로그램은 스택 맨 위 근처에 소량의 메모리를 반복적으로 할당 및 할당 해제하기 때문에 스택 맨 위의 위치가 엄청나게 많이 사용되고 재사용되므로 (대부분의 응용 프로그램에서는 99 % 이상) 캐시 메모리를 사용하여 스택 액세스를 처리 할 수 ​​있습니다.

반대로, 애플리케이션이 연속 정보를 저장하기 위해 반복적으로 힙 객체를 생성하고 포기하는 경우, 생성 된 모든 스택 객체의 모든 버전은 주 메모리에 기록되어야합니다. CPU가 시작한 캐시 페이지를 재활용하려고 할 때까지 이러한 객체의 대다수가 완전히 쓸모 없더라도 CPU는 그것을 알 방법이 없습니다. 결과적으로 CPU는 쓸모없는 정보의 느린 메모리 쓰기를 수행하는 데 많은 시간을 낭비해야합니다. 속도를위한 레시피는 아닙니다.

고려해야 할 또 다른 사항은 많은 경우 루틴이 종료 된 후에 루틴에 전달 된 오브젝트 참조가 사용되지 않는다는 것을 아는 것이 유용하다는 것입니다. 매개 변수 및 로컬 변수가 스택을 통해 전달되고 루틴 코드를 검사하여 전달 된 참조의 사본이 지속되지 않는 것으로 밝혀지면 루틴을 호출하는 코드는 외부 참조가없는 경우이를 확인할 수 있습니다. 호출 전에 객체가 존재하면 이후에는 아무것도 존재하지 않습니다. 반대로, 매개 변수가 힙 오브젝트를 통해 전달 된 경우, "루틴 리턴 후"와 같은 개념은 코드가 연속의 사본을 유지하면 루틴이 다음에 두 번 이상 "리턴"할 수 있기 때문에 다소 어려워집니다. 단일 통화.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.