Post List

2018/03/04

Systen__Heap


이번에는 힙에 대해 알아보겠습니다. 힙은 이전에 알아보았던 메모리 공간 중 하나입니다
메모리를 사용하기 위해 C언어의 경우 malloc free, C++에서는 new 또는 delete 키워드를 사용해 메모리를 할당하고 해제하는 방법을 알고 있습니다.
이러한 경우 프로세스를 생성할 더불어 생성되는 , 정확히 말하면 1Mbyte 크기의 디폴트 영역에 메모리를 할당하게 됩니다. 디폴트 힙은 프로세스에 기본적으로 할당되는 힙이라 하여 프로세스 힙이라고도 부릅니다.

디폴트 컨트롤
위에서 말한 디폴트 힙의 기본 크기는 1M바이트입니다. 그러나 링커옵션을 통해서 변경이 가능하답니다. 아래와 같은 링커 옵션을 통해, 혹은 이전에 스택 예약 크기를 지정하는 방법에 대해 이야기했던 적이있습니다.

같은 항목 위에 Heap Reserve Size Heap Commit Size 통해 RESERVE 상태에 놓이게 되는 힙의 전체 크기와 이 중에서 초기에 COMMIT 상태로 메모리 크기를 지정할 있습니다.


Q : 디폴트 힙의 기본 크기가 1M바이트이면 malloc 함수나 new연산자를 통해 할당할 1M바이트가 초과되면 어떻게 될까요?

A : 디폴트 힙의 기본 크기 1M바이트는 힙이 생성된 직후의 초기 크기를 말하며, 그 크기를 늘려야 할 경우 필요에 따라서 Windows 시스템이 알아서 늘려준답니다. 결국 번거롭게 디폴트 크기를 개발자가 정해줄 필요가 없다는 결론이 나옵니다. 그러나 디폴트 크기를 정해줌으로 얻게 되는 장점도 있습니다. 프로세스가 실행 중인 상태에서 새로운 메모리 영역을 할당하는 것은(RESERVE 상태로 예약하는 ) 시간이 제법 걸리는 작업입니다. 따라서 필요한 크기만큼 여유 있는 디폴트 힙을 요청해 둔다면 그만큼의 시간을 아낄 있습니다.

디폴트 이외에 Windows 시스템 함수 호출을 통해서 생성되는 힙을 가리켜 동적 힙이라 합니다. 영어로는 Dynamic Heap으로 표기합니다. 일반적으로 개발을 할때 힙이란 용어를 별다른 언급없이 사용할 Dynamic Heap 의미합니다. 리스트 자료구조를 예로 들면서 힙을 추가로 생성하는 것이 어떠한 장점을 가져다 주는지 알아보겠습니다.

먼저, 메모리 단편화의 최소화에 따른 성능 향상을 불러옵니다
A라는 기능을 위해서 A 생성하고 B라는 기능을 위해서 B 생성한다면 아래와 같은 구조로 메모리가 할당됩니다.



반면에 이것을 하나의 디폴트 힙에서 처리한다면 그림의 오른쪽과 같은 구조가 됩니다. 실제로는 훨씬 복잡하게 단편화가 발생할 소지가 높습니다.
일단 A,B,C 힙을 미리 선언하면 할당된 페이지가 RESERVE 상태에 놓이기 때문에 메모리 단편화가 발생하지 않습니다. 반면에 디폴트 힙을 활용할 경우 프로그램 실행과정에서 무작위 메모리 할당 그에 따른 크기의 증가에 의해 메모리 단편화가 심하게 발생합니다. 단편화가 심하다는 것은 프로그램의 로컬리티 특성이 낮아진다는 것을 의미하며, 이는 성능에 많은 영향을 미치게 됩니다. 필요에 맞게 추가적인 힙을 생성해서 활용한다면, 성능 향상도 기대할 있습니다.

두번째는 동기화 문제에 자유로워짐으로 인한 성능 향상입니다
멀티 쓰레드 프로그래밍을 쓰레드별로 사용할 힙을 별도로 할당해 주는 것은 의미 있는 일입니다. 일반적으로 힙은 쓰레드가 공유하는 메모리 영역입니다. 때문에 이상의 쓰레드가 동시접근 할때 문제가 발생할 소지가 있어서 Windows 내부적으로 동기화 처리를 해주고 있습니다. 일반적으로 동기화라고 하면 이상의 쓰레드가 힙에 선언된 변수를 동시에 참조하는 상황이며, 이는 세마포어나 뮤텍스를 선언해 해결할 있습니다
하지만 여기서 말하는 동시접근은 메모리의 할당과 해제입니다. 같은 주소 번지에 이상의 쓰레드가 동시에 메모리를 할당 해제하는 상황이 발생할 경우 메모리 오류가 발생합니다. 때문에 디폴트 프로세스 힙은 쓰레드가 메모리를 할당하려고 하는 경우 내부적으로 동기화 처리를 하고 있습니다.
그런데 하나의 쓰레드당 독립된 하나의 힙을 할당할 경우 동기화 처리를 필요가 없고 이것으로 인한 성능 향상을 기대할 있게됩니다.

마지막으로 위에서 언급되었던 메모리 단편화에 대해서 알아보겠습니다. 
여기서 메모리 단편화가 어떠한 상황에서 발생하고 무엇 때문에 성능이 저하되는지 알아보겠습니다.
먼저 리스트와 트리의 자료구조를 가지고 예를 들어보겠습니다. 디폴트 힙을 활용해서 리스트와 트리를 구성하려고 합니다. 그러면 아래와 같은 상황이 전개될 입니다.

리스트와 트리 노드를 추가할 때마다 메모리 할당이 이루워집니다. 이렇게 해서 4개의 페이지에 걸쳐 메모리 할당이 이뤄졌다고 가정합니다. 그렇다면 그림과 같이 리스트 노드와 트리 노드가 심하게 섞이게 됩니다.
상황에서 리스트를 구성하는 모든 노드들을 참조하고 싶다고 가정할 , 번째 노드부터 시작해서 마지막 노드까지 참조해 들어갑니다. 과정에서 4개의 페이지는 모두 메인 메모리에 올라와 있어야 합니다. 4개의 페이지에 노드가 분산 저장되어 있기 때문이죠. 그런데 메인 메모리에 여유가 없어서 순간에 하나의 페이지만 메인 메모리에 있다면, 최악의 경우에는 노드를 참조할 때마다 하드디스크에 저장되어 있는 페이지를 메인 메모리로 옮겨 와야 합니다. 물론 이런 일은 발생하지 않지만 아주 빈번한 페이지 이동이 예상됩니다.
해당 노드가 메인 메모리에 존재하지 않는 상황을 가리켜 Page Fault 하는데, Page Fault 발생하면 하드디스크에 저장되어 있는 페이지를 메인 메모리에 올려 놓는 수고를 해야만 합니다. 만약에 리스트를 위한 그리고 트리를 위한 힙을 독립해서 생성하였다면 다음과 같은 구조로 페이지가 구성됩니다.


번째 그림에 비해서 상대적으로 훨씬 적은 Page Fault 발생하게 됩니다. 로컬리티가 훨씬 좋아졌지요.