사용자 정의 할당자는 성능 향상, 메모리 사용 효율성 증대, 그리고 빈번한 소량 메모리 할당 문제를 해결할 수 있습니다
전인
최근 네트워크 데이터 패킷 개발을 진행하면서 빈번하게 작은 메모리 블록을 할당하고 해제해야 하는데, 메모리 풀을 사용하려 했더니 기존 메모리 풀들을 살펴보니 이런 것을 발견했다
https://github.com/cacay/MemoryPool
인터페이스를 보니까 좀 이상하다, 이 메모리 풀 구현이 어떻게 이렇게 특이할까. MemoryPool
的实现逻辑,是在申请固定大小的内存空间。看过boost的内存池接口,提供的是一个模板,用的时候进行实例化。正巧这个库已经有文章进行过介绍,提到了allocator
개념 말이야.
wiki
C++ 프로그래밍에서 할당자(allocator)는 C++ 표준 라이브러리의 중요한 구성 요소입니다. C++ 라이브러리에는 연결 리스트, 집합 등 다양한 “컨테이너” 데이터 구조가 정의되어 있으며, 이 컨테이너들의 공통적인 특징 중 하나는 프로그램 실행 시간에 크기를 변경할 수 있다는 것입니다. 이를 구현하기 위해 동적 메모리 할당이 필수적이므로, 할당자는 컨테이너의 메모리 할당 및 해제 요청을 처리하는 데 사용됩니다. 다시 말해, 할당자는 표준 템플릿 라이브러리(STL) 컨테이너의 메모리 관리에 대한 저수준 세부 사항을 캡슐화합니다. 기본적으로 C++ 표준 라이브러리는 자체 제공하는 범용 할당자를 사용하지만, 필요에 따라 프로그래머는 자신만의 할당자를 정의하여 대체할 수 있습니다.
분포자는 원래 C++ 표준 템플릿 라이브러리(STL)의 일부로 알렉산드르 스테파노프가 “라이브러리를 더욱 유연하게 만들고, 하위 데이터 모델에 의존하지 않는 방법”을 목표로 발명했지만, C++ 표준 위원회는 STL을 표준으로 채택하는 과정에서 완전한 데이터 모델 추상화가 용납할 수 없는 성능 저하를 야기한다는 것을 깨달았습니다. 이러한 절충안으로 인해 표준에서 할당자에 대한 제약이 더욱 엄격해졌고, 결과적으로 현재 표준에 설명된 할당자의 사용자 정의 가능성은 스테파노프의 원래 구상에 비해 크게 제한되었습니다.
할당기 사용자 정의는 제한적일 수 있지만, 많은 경우 다양한 메모리 공간(공유 메모리 및 재활용 메모리와 같은)에 대한 접근 방식을 캡슐화하거나 메모리 풀을 사용한 메모리 할당 시 성능을 향상시키기 위해 여전히 사용자 정의 할당기가 필요합니다. 또한, 빈번하게 소량의 메모리를 할당하는 프로그램에서는 메모리 점유 및 실행 시간 측면에서 전용으로 최적화된 할당기를 도입하면 상당한 이점을 얻을 수 있습니다.
使用需求
사용자 정의 할당기를 사용하는 주된 이유 중 하나는 성능 향상입니다. 전용 사용자 정의 할당기는 프로그램의 성능을 높이거나, 메모리 사용 효율성을 개선하거나, 이 둘 모두를 달성할 수 있습니다 [4][8]. 기본 할당기는 new 연산자를 사용하여 저장 공간을 할당하는데, 이는 종종 C 언어의 힙 할당 함수(malloc())를 통해 구현됩니다 [9]. 힙 할당 함수는 일반적으로 일시적으로 많은 메모리를 할당하는 데 최적화되어 있으므로, 대량의 메모리를 한 번에 할당해야 하는 컨테이너(예: 벡터, 양방향 큐)의 경우 기본 할당기는 효율적인 경향이 있습니다 [8]. 그러나 연결 리스트와 같이 빈번하게 소량의 메모리를 할당해야 하는 컨테이너의 경우 기본 할당기를 사용하면 일반적으로 효율성이 매우 낮습니다 [4][9]. 또한 malloc() 기반의 기본 할당기는 낮은 지역성[4] 및 메모리 조각화 발생 가능성[4][9]과 같은 여러 가지 문제점을 가지고 있습니다.
간단히 말해서, 이 부분은 (……)이 해당 표준의 할당기 관련 《나에게 꿈이 있어요》와 같습니다. 꿈이 실현되기 전에는 이식성을 중시하는 프로그래머들은 상태 없는 사용자 정의 할당기를 사용할 수밖에 없을 것입니다. ——스코트 메이스, 《Effective STL》 이러한 점을 감안하여, 이 경우에 사람들은 빈번한 소량 할당 문제를 해결하기 위해 메모리 풀 기반 할당기를 자주 사용한다[8]. 기본 “요청 시 할당” 방식과 달리, 메모리 풀 기반 할당기를 사용하는 경우 프로그램은 미리 대용량 메모리(즉, “메모리 풀”)를 할당하고, 필요할 때 사용자 정의 할당기는 요청자에게 풀 내 메모리의 포인터를 반환하기만 한다. 객체 소멸 시에는 실제로 메모리를 해제하지 않고, 메모리 풀의 수명이 종료될 때 비로소 해제를 지연시킨다[주 1][8].
“사용자 정의 할당기”라는 주제에 대해 스콧 메이어스의 “Effective STL”과 안드레이 알렉산더레스쿠의 “Modern C++ Design”과 같이 여러 C++ 전문가 및 관련 저자들이 참여하여 논의해 왔습니다. 메이어스는 특정 타입 T에 대한 모든 할당기 인스턴스가 동일해야 한다면 이식 가능한 할당기 인스턴스는 상태를 포함해서는 안 된다고 지적했습니다. C++ 표준은 라이브러리 구현자가 상태 기반 할당기를 지원하도록 권장하지만[문헌 4], 메이어스는 관련 단락이 “(보이는 듯한)아름다운 관점”이지만 거의 말도 안 되는 소리이며, 할당기 제한이 “지나치게 엄격하다”고 주장했습니다[4]. 예를 들어, STL의 list는 splice 메서드를 허용하는데, 이는 list 객체 A의 노드가 다른 list 객체 B로 직접 이동될 수 있다는 의미입니다. 이는 A의 할당기가 할당한 메모리가 B의 할당기에 의해 해제될 수 있어야 하므로 A와 B의 할당기 인스턴스가 동일해야 함을 시사합니다. 메이어스의 결론은 할당기를 정적 메서드를 사용하는 타입으로 정의하는 것이 가장 좋다는 것입니다. 예를 들어, C++ 표준에 따르면 할당기는 rebind 메서스를 구현한 other 클래스 템플릿을 제공해야 합니다.
또 다른 예로, Bjarne Stroustrup는 그의 저서 《C++ 프로그램 설계 언어》에서 “각 객체 정보가 다를 수 없도록 할당자를 엄격하게 제한하는 것은 큰 문제가 아니다.”라고 주장하며 대부분의 할당자는 상태를 필요로 하지 않으며, 심지어 상태가 없는 경우 성능이 더 좋을 수도 있다고 지적합니다. 그는 메모리 풀 할당자, 공유 메모리 할당자 및 가비지 컬렉션 할당자의 세 가지 사용자 정의 할당자 사용 사례를 제시하고 내부 메모리 풀을 활용하여 소량의 메모리를 빠르게 할당/해제하는 할당자 구현을 보여줍니다. 하지만 이러한 최적화는 이미 그가 제공한 예시 할당자에서 구현되었을 수도 있다고 언급합니다[3].
사용자 정의 할당기의 또 다른 용도는 메모리 관련 오류를 디버깅하는 것입니다[10]. 이를 위해 추가 메모리를 할당하여 디버그 정보를 저장하는 할당기를 작성할 수 있습니다. 이러한 할당기는 메모리가 동일한 할당기에서 할당 및 해제되도록 보장할 뿐만 아니라, 캐시 오버플로로부터 프로그램의 안전을 어느 정도 보호할 수도 있습니다[11].