답변:
여기서 언급했듯이 Intel TBB의 사용자 정의 STL 할당자는 단일 스레드를 변경하여 멀티 스레드 응용 프로그램의 성능을 크게 향상시키는 것을 보았습니다.
std::vector<T>
에
std::vector<T,tbb::scalable_allocator<T> >
(이것은 TBB의 멋진 스레드 전용 힙을 사용하도록 할당자를 전환하는 빠르고 편리한 방법입니다. 이 문서의 7 페이지 참조 )
사용자 지정 할당자가 유용 할 수있는 영역 중 하나는 게임 개발, 특히 게임 콘솔에서 메모리가 적고 스왑이 없기 때문에 게임 개발입니다. 이러한 시스템에서는 각 하위 시스템을 엄격하게 제어하여 중요하지 않은 시스템 하나가 중요한 시스템에서 메모리를 훔칠 수 없도록해야합니다. 풀 할당 자 같은 다른 것들은 메모리 조각화를 줄이는 데 도움이 될 수 있습니다. 다음 주제에서 길고 자세한 논문을 찾을 수 있습니다.
벡터가 메모리 매핑 파일의 메모리를 사용할 수 있도록 mmap 할당 기에서 작업하고 있습니다. 목표는 mmap에 의해 매핑 된 가상 메모리에 직접있는 스토리지를 사용하는 벡터를 갖는 것입니다. 우리의 문제는 복사 오버 헤드없이 실제로 큰 파일 (> 10GB)을 메모리로 읽는 것을 향상시키는 것이므로이 사용자 지정 할당자가 필요합니다.
지금까지 std :: allocator에서 파생 된 사용자 지정 할당 자의 골격을 가지고 있으므로 자체 할당자를 작성하는 것이 좋은 출발점이라고 생각합니다. 이 코드를 원하는 방식으로 자유롭게 사용하십시오.
#include <memory>
#include <stdio.h>
namespace mmap_allocator_namespace
{
// See StackOverflow replies to this answer for important commentary about inheriting from std::allocator before replicating this code.
template <typename T>
class mmap_allocator: public std::allocator<T>
{
public:
typedef size_t size_type;
typedef T* pointer;
typedef const T* const_pointer;
template<typename _Tp1>
struct rebind
{
typedef mmap_allocator<_Tp1> other;
};
pointer allocate(size_type n, const void *hint=0)
{
fprintf(stderr, "Alloc %d bytes.\n", n*sizeof(T));
return std::allocator<T>::allocate(n, hint);
}
void deallocate(pointer p, size_type n)
{
fprintf(stderr, "Dealloc %d bytes (%p).\n", n*sizeof(T), p);
return std::allocator<T>::deallocate(p, n);
}
mmap_allocator() throw(): std::allocator<T>() { fprintf(stderr, "Hello allocator!\n"); }
mmap_allocator(const mmap_allocator &a) throw(): std::allocator<T>(a) { }
template <class U>
mmap_allocator(const mmap_allocator<U> &a) throw(): std::allocator<T>(a) { }
~mmap_allocator() throw() { }
};
}
이를 사용하려면 다음과 같이 STL 컨테이너를 선언하십시오.
using namespace std;
using namespace mmap_allocator_namespace;
vector<int, mmap_allocator<int> > int_vec(1024, 0, mmap_allocator<int>());
예를 들어 메모리가 할당 될 때마다 기록하는 데 사용할 수 있습니다. 필요한 것은 리 바인드 구조체입니다. 그렇지 않으면 벡터 컨테이너가 수퍼 클래스 할당 / 할당 해제 메소드를 사용합니다.
업데이트 : 메모리 매핑 할당자는 이제 https://github.com/johannesthoma/mmap_allocator 에서 사용할 수 있으며 LGPL입니다. 프로젝트에 자유롭게 사용하십시오.
사용자 지정 STL 할당 자로 C ++ 코드를 작성하지는 않았지만 HTTP 요청에 응답하는 데 필요한 임시 데이터를 자동으로 삭제하기 위해 사용자 지정 할당자를 사용하는 C ++로 작성된 웹 서버를 상상할 수 있습니다. 사용자 지정 할당자는 응답이 생성되면 모든 임시 데이터를 한 번에 해제 할 수 있습니다.
사용자 지정 할당 자 (사용했던)의 또 다른 유스 케이스는 함수의 동작이 입력의 일부에 의존하지 않음을 입증하기 위해 단위 테스트를 작성하는 것입니다. 사용자 지정 할당자는 임의의 패턴으로 메모리 영역을 채울 수 있습니다.
GPU 또는 다른 보조 프로세서로 작업 할 때 특별한 방식으로 주 메모리에 데이터 구조를 할당하는 것이 때때로 유리합니다 . 메모리를 할당하는 이 특별한 방법 은 편리한 방식으로 커스텀 할당 자에서 구현 될 수 있습니다.
액셀러레이터를 사용할 때 액셀러레이터 런타임을 통한 사용자 지정 할당이 유리한 이유는 다음과 같습니다.
여기서는 사용자 지정 할당자를 사용하고 있습니다. 다른 사용자 지정 동적 메모리 관리 문제를 해결 해야한다고 말할 수도 있습니다 .
배경 : malloc, calloc, free 및 다양한 연산자의 new 및 delete에 대한 과부하가 있으며 링커는 STL을 사용하여 우리를 위해 이것을 행복하게 만듭니다. 이를 통해 자동 작은 객체 풀링, 누수 감지, 할당량 채우기, 무료 채우기, 센트리를 사용한 패딩 할당, 특정 할당량에 대한 캐시 라인 정렬 및 지연되지 않은 할당과 같은 작업을 수행 할 수 있습니다.
문제는 우리가 임베디드 환경에서 실행하고 있다는 것입니다. 실제로 오랜 시간 동안 누출 감지 계정을 제대로 수행하기에 충분한 메모리가 부족합니다. 최소한 표준 RAM이 아닌 사용자 지정 할당 기능을 통해 다른 곳에서 사용할 수있는 RAM이 있습니다.
해결 방법 : 확장 힙을 사용하는 사용자 지정 할당자를 작성 하고 메모리 누수 추적 아키텍처의 내부 에서만 사용하십시오. 그 밖의 모든 것은 누수 추적을 수행하는 일반적인 새 / 삭제 과부하로 기본 설정됩니다. 이렇게하면 추적기 자체 추적을 피할 수 있습니다 (추적 기능도 제공합니다. 추적기 노드의 크기를 알고 있습니다).
우리는 또한 같은 이유로 함수 비용 프로파일 링 데이터를 유지하기 위해 이것을 사용합니다. 스레드 스위치뿐만 아니라 각 함수 호출 및 리턴에 대한 항목을 작성하면 비용이 많이들 수 있습니다. 사용자 지정 할당 기는 다시 큰 디버그 메모리 영역에서 더 작은 할당량을 제공합니다.
한 가지 필수 상황 : 모듈 (EXE / DLL) 경계에서 작동해야하는 코드를 작성할 때 하나의 모듈에서만 할당 및 삭제를 유지하는 것이 중요합니다.
내가 본 곳은 Windows의 플러그인 아키텍처였습니다. 예를 들어, DLL 경계를 가로 질러 std :: string을 전달하는 경우, 문자열의 재 할당은 DLL의 힙이 아닌 *에서 시작된 힙에서 발생해야합니다 *.
* CRT에 동적으로 연결하는 것처럼 어쨌든 효과가있을 수 있으므로 실제로 이보다 더 복잡합니다. 그러나 각 DLL에 CRT에 대한 정적 링크가 있으면 팬텀 할당 오류가 계속 발생하는 고통의 세계로 향하고 있습니다.
내가 이것을 사용했던 시간의 한 예는 리소스가 제한된 임베디드 시스템으로 작업하는 것이 었습니다. 2k의 램이없고 프로그램에서 해당 메모리의 일부를 사용해야한다고 가정 해 봅시다. 스택에 있지 않은 곳에 4-5 개의 시퀀스를 저장해야하며 추가로 이러한 것들이 저장되는 위치에 대해 매우 정확하게 액세스해야합니다. 이는 자신의 할당자를 작성하려는 상황입니다. 기본 구현은 메모리를 조각화 할 수 있습니다. 메모리가 충분하지 않고 프로그램을 다시 시작할 수없는 경우 허용되지 않을 수 있습니다.
내가 작업 한 프로젝트 중 일부는 저전력 칩에서 AVR-GCC를 사용하는 것이 었습니다. 가변 길이의 시퀀스 8 개를 저장해야하지만 최대 값은 알려져 있습니다. 메모리 관리 의 표준 라이브러리 구현malloc / free 주위의 얇은 래퍼로 할당 된 모든 메모리 블록 앞에 할당 된 메모리 조각의 끝을지나 포인터를 붙여 항목을 배치 할 위치를 추적합니다. 새로운 메모리 조각을 할당 할 때 표준 할당자는 각 메모리 조각을 걸어서 요청 된 메모리 크기에 맞는 다음 블록을 찾아야합니다. 데스크탑 플랫폼에서는이 항목이 매우 빠르지 만 이러한 마이크로 컨트롤러 중 일부는 매우 느리고 원시적이라는 점을 명심해야합니다. 또한 메모리 조각화 문제는 우리가 실제로 다른 접근 방식을 택할 수밖에 없었던 거대한 문제였습니다.
우리가 한 일은 우리 자신의 메모리 풀 을 구현하는 것이 었습니다 . 각 메모리 블록은 우리가 필요로하는 가장 큰 순서에 맞을만큼 충분히 컸습니다. 이로 인해 고정 크기의 메모리 블록이 미리 할당되어 현재 사용중인 메모리 블록이 표시되었습니다. 특정 블록이 사용 된 경우 각 비트가 표시되는 8 비트 정수를 유지하여이 작업을 수행했습니다. 우리는 전체 프로세스를 더 빠르게 만들기 위해 메모리 사용을 교환했습니다.
임베디드 시스템의 맥락에서 사용자 지정 할당자를 작성하는 것을 볼 수있는 다른 경우가 있습니다. 예를 들어 시퀀스의 메모리가 이러한 플랫폼 에서 자주 볼 수있는 것처럼 메인 램에 있지 않은 경우 .
Andrei Alexandrescu의 CppCon 2015 토크 할당 자와의 의무적 인 링크 :
https://www.youtube.com/watch?v=LIb3L4vKZ7U
좋은 점은 그것들을 고안하는 것만으로 어떻게 사용하는지에 대한 아이디어를 생각하게 만드는 것입니다 :-)
공유 메모리의 경우 컨테이너 헤드뿐만 아니라 포함 된 데이터도 공유 메모리에 저장해야합니다.
Boost :: Interprocess 의 할당 자가 좋은 예입니다. 그러나 여기에서 읽을 수 있듯이 모든 STL 컨테이너 공유 메모리를 호환 가능하게 만들기 위해이 모든 것만으로는 충분하지 않습니다 (다른 프로세스에서 서로 다른 매핑 오프셋으로 인해 포인터가 "깨질 수 있습니다").
언젠가 나는이 솔루션이 STL 컨테이너 용 Fast C ++ 11 할당 자 에게 매우 유용하다는 것을 알았습니다 . VS2017 (~ 5x) 및 GCC (~ 7x)에서 STL 컨테이너 속도를 약간 높입니다. 메모리 풀에 기반한 특수 목적의 할당 자입니다. 요구하는 메커니즘 덕분에 STL 컨테이너와 함께 사용할 수 있습니다.
저는 개인적으로 Loki :: Allocator / SmallObject를 사용하여 작은 개체의 메모리 사용을 최적화합니다. 중간 크기의 작은 개체 (1-256 바이트)로 작업해야하는 경우 좋은 효율성과 만족스러운 성능을 보여줍니다. 다양한 크기의 작은 객체를 적당량 할당하는 것에 대해 이야기하면 표준 C ++ 새로운 / 삭제 할당보다 최대 30 배 더 효율적일 수 있습니다. 또한 "QuickHeap"이라는 VC 전용 솔루션이 있으며 최상의 성능을 제공합니다 (할당 및 할당 해제 작업은 각각 최대 99 개의 할당 / 반환되는 블록의 주소를 읽고 쓰기 만 함). — 설정 및 초기화에 따라 다름) 그러나 상당한 오버 헤드 비용이 발생합니다. 익스텐트 당 두 개의 포인터와 각각의 새 메모리 블록에 대해 하나의 추가 포인터가 필요합니다. 그것'
표준 C ++ new / delete 구현의 문제점은 일반적으로 C malloc / free 할당을위한 랩퍼이며 1024+ 바이트와 같은 더 큰 메모리 블록에 적합하다는 것입니다. 성능면에서 눈에 띄는 오버 헤드가 있으며 때로는 매핑에 사용되는 추가 메모리가 있습니다. 따라서 대부분의 경우 사용자 지정 할당자는 작은 (≤1024 바이트) 객체를 할당하는 데 필요한 추가 메모리의 양을 최소화하고 성능을 최대화하는 방식으로 구현됩니다.