Loading
2022. 11. 2. 23:03 - lazykuna

Sized Memory Deallocation

메모리 할당부터 해제까지

먼저 설명하기 전에 앞서서, 현재 시스템에서 메모리를 어떻게 관리하고 있는지 간단하게 알아보고 넘어갈 필요가 있어 적어둡니다.

하드웨어 상에서는 우리가 흔히 아는 것처럼 페이지를 할당받아서, 해당 페이지를 이용하여 메모리를 관리하는 것으로 우리는 흔히 알고 있습니다. 이러한 메모리의 주소를 프로세스별로 할당하기 위해 logical memory, TLB 등의 개념이 추가로 사용되는 것으로 알려져 있습니다.

출처: https://rebro.kr/178

하지만 여기서 혼란스러운 점은, 우리가 실제로 사용하는 메모리 관리를 위한 인터페이스는 보통 malloc()free() 라는 것입니다. 또, 프로세스 메모리를 보면 힙과 스택이 있는데, 이러한 구조는 위에서 배운 logical memory 개념과 잘 들어맞지 않는 것처럼 보입니다.

일단 메모리 할당 부분부터 알아봅시다. 힙 메모리와 같은 경우, 확장을 수행할 때는 내부적으로 sbrk() (또는 brk()) 를 수행하여 메모리를 확보하게 됩니다. 이 때 힙을 확장하면서 (필요하다면) 페이지를 새로 받아오게 됩니다. 그리고 malloc 도 내부적으로는 sbrk 호출합니다. 다만 페이지보다 작은 크기의 malloc을 할당하면 새 페이지를 받아오지 않을수도 있겠죠. 그리고 sbrk랑 혼용할 수 없는 것도 이러한 이유 때문입니다.

  • malloc의 경우에는 free-list 를 돌면서 할당 가능한 “블록”을 찾습니다. #구체적인 구현 방법
  • TMI: 대량의 메모리를 할당할 때는 heap도 아니고 mmap를 사용하여 별도의 가상 메모리 주소(페이지)를 부여하게 됩니다.

Heap의 free-list 사진

메모리 해제의 경우 마찬가지로 free-list를 이용하는데, 파편화 등을 막기 위해서 merging 등의 최적화 작업들이 수행됩니다. 그렇게 하기 위해서는 할당받은 메모리의 끝 영역을 알 필요가 있어야 합니다. 크기를 알아야 한다는 이야기죠. 여기에서 할당 영역의 크기를 알고 있으면 굳이 free-list를 찾을 필요가 없다는 결론이 나오는 것입니다.

  • 실제로는 보안의 이유로 ASLR 등의 기법이 사용되어 더 복잡합니다
  • 이러한 메모리 할당과 해제의 매커니즘은 운영체제마다 다르다는 점도 염두에 둡시다. (맥에서의 남은 메모리용량이 윈도우와 다르게 표시되는데도 한 몫 기여함) (큰 틀에서는 유사할 수 있음)

크기를 지정하는 메모리 할당 해제

크기를 지정하는 메모리 할당 해제 (Sized memory deallocation)은 메모리를 할당받은 크기를 기억해 두었다가 이를 해제할 때 그대로 사용하는 것입니다.

void free_sized(void *ptr, size_t size);

사용하는 측에서는 메모리 할당 크기를 별도로 같이 기억해 두면 된다는 것 이외에 크게 차이점은 없을 것입니다.

시스템 구조상의 차이?

시스템 구조상의 차이는 없습니다. 어쨌거나 하위 호환을 위해서 기본형인 free(*ptr) 는 계속 지원을 하고 있거든요. 따라서 size를 관리하는 해시 테이블은 그대로 있어야 합니다.

  • 물론 page fault나 page merging과 같은 기본적인 기능들은 동일하게 기능합니다.

메모리 관리는 자기가 알아서 하자

이런 설계는 “자신이 할당한 메모리는 자신이 관리하자”라는 철학에 기반합니다. 사실 C/C++도 할당한 메모리를 본인이 관리해야 한다는 점에 있어 그러한 철학이 반영되어 있는 건 맞지만, 정작 “크기”에 대한 요소는 고려하지 않고 있는 셈입니다.

성능 차이

메모리 해제시 30% 가량의 부하를 줄일 수 있다고 합니다. 메모리 할당 크기를 인자로 넘겨주기 때문에, 할당 정보에 대한 메타데이터를 직접 찾아갈 필요가 없는게 큰 강점으로 작용한다고 하네요 (don’t need to look up).

  • look up 을 하지 않는다는 것은 단순 시간을 단축하는 것 뿐만 아니라 불필요한 캐싱을 하지 않음을 의미하기 때문에 더욱 성능에 의미하는 바가 큽니다.
  • 실제로 아래의 메모리 관련 조작 연산에서 Memory-Free가 차지하는 비용이 생각보다 큽니다. memory-intense한 작업이 아니면 메모리 연산이 의외로 비중이 크지 않은 것을 볼 수 있습니다.

추가적으로 보안에 대한 이점도 가지고 있다고 합니다. incorrect size deallocation이 발생하면 프로세스가 aborted 되기 때문이라고 합니다.

지원하는 언어

  • C++과 같은 경우, C++14에서 operator delete 에 추가적인 인자를 넘겨줌으로서 sized-allocator을 지원합니다.
  • Rust에서는 “암묵적 가변 길이 배열 해제(implicit size deallcation)”을 지원한다고 합니다.

TMI

  • 성능이 더 우수한데도 불구하고 불구하고 크기 지정 없는 free() 를 사용하는 것은 C 언어의 초기 디자인의 결함 때문이라는 이야기가 있습니다. 사실 메모리 할당과 해제를 담당하는 mallocfree 는 문자열을 할당하기 위함이었다는 이야기가 있는데, 문자열의 끝을 NULL CHARACTER로 디자인 한 것도 그와 같은 맥락에서였다고 합니다. 현재는 이를 잘못된 디자인으로 일컫고 있다고.
    • 당시 가변 길이 배열(size + string 형태)을 사용하지 않았던 이유가 성능적 측면에서의 제약도 있었고, “가변 길이 배열” 이라는 생소한 개념 자체를 만들고 싶지 않아서라는 이야기도 있습니다. #1 #2
    • 출처가 부정확한 점 죄송합니다. 정확한 출처 찾기가 어렵네요.
  • 최근의 언어들은 모두 내부적으로 size memory deallocation을 채택하고 있습니다. 문자열을 예시로 들면, 비슷한 low-level 언어인 Rust 도 length 정보를 저장해두어 메모리를 관리하고 있습니다. C++도 Implicit은 아니지만 sized-memory deallocation을 지원합니다. C만 아직 공식 지원이 없는...

참고