지식이 늘었다/C,C++

std::allocator / new, delete overloading 소개 및 활용

moneydeveloper 2020. 8. 28. 15:41
반응형

이번에는 실제 개발하면서 유용하게 사용하고 있는 것들에 대해서 소개를 해볼까 한다. 

 

1. std::allocator 

stl container 내부에서 heap 메모리 할당시에 사용하는 할당자를 을 직접 구현할 수 있도록 도와주는 class template 이다. 처음 접할 땐 이걸로 뭘할 수 있는지 바로 떠오르긴 힘들지만 개발을 하다보니 이 template 을 통해 아주 많은 것들을 할 수 있다는것을 알게 되었었다. 

 

referece site : https://en.cppreference.com/w/cpp/memory/allocator

 

std::allocator - cppreference.com

template< class T > struct allocator; (1) template<> struct allocator ; (2) (deprecated in C++17) (removed in C++20) The std::allocator class template is the default Allocator used by all standard library containers if no user-specified allocator is provid

en.cppreference.com

사용방법은 간단하다. 

 

allocator 에 선언되어있는 member function 들을 재정의하여 template class 를 작성해주고 
stl container 에 allocator 에 넣어주면 된다. 

 

ex)

namespace md
{
	template <typename T>
	class allocator
	{
	public:
		// type definitions
		typedef T        value_type;
		typedef T*       pointer;
		typedef const T* const_pointer;
		typedef T&       reference;
		typedef const T& const_reference;
		typedef std::size_t    size_type;
		typedef std::ptrdiff_t difference_type;

		// rebind allocator to type U
		template <class U>
		struct rebind {
			typedef allocator<U> other;
		};

		// return address of values
		pointer address(reference value) const {
			return &value;
		}
		const_pointer address(const_reference value) const {
			return &value;
		}

		/* constructors and destructor
		 * - nothing to do because the allocator has no state
		 */
		allocator() throw() {}
		allocator(const allocator&) throw() {}		
		template <class U>
		allocator(const allocator<U>&) throw() {}
		~allocator() throw() {}

		// return maximum number of elements that can be allocated
		size_type max_size() const throw() {
			return std::numeric_limits<std::size_t>::max() / sizeof(T);
		}

		// allocate but don't initialize num elements of type T
		pointer allocate(size_type num, const void* = 0) {
			// print message and allocate memory with global new
			pointer ret = (pointer)(::operator new(num * sizeof(T)));
			return ret;
		}

		// initialize elements of allocated storage p with value value
		void construct(pointer p, const T& value) {
			// initialize memory with placement new
			new((void*)p)T(value);
		}

		// destroy elements of initialized storage p
		void destroy(pointer p) {
			// destroy objects by calling their destructor
			p->~T();
		}

		// deallocate storage p of deleted elements
		void deallocate(pointer p, size_type num) {
			// print message and deallocate memory with global delete
			::operator delete((void*)p);
		}
		template <class U> bool
			operator==(allocator<U> const&) const
		{
			return NVS_TRUE;
		}
		template <class U> bool
			operator!=(allocator<U> const&) const
		{
			return NVS_FALSE;
		}
	};
}

std::vector<int, md::allocator> tintVector

위 코드에서 중요한 부분은 allocate / deallocate 부분이다. 이부분에 내가 사용할 할당자를 사용하면된다. 

 

 

2. new / delete overloading

위 allocator 는 선언된 container 에만 적용된다면 new / delete overloading 은 
프로젝트 전체의 new / delete 에 적용된다고 보면된다. 

 

referece site 

new :  en.cppreference.com/w/cpp/memory/new/operator_new

delete : en.cppreference.com/w/cpp/memory/new/operator_delete

 

사용방법은 역시 간단하다.  

ex ) 가장 단순한 new /delete 에 대한 overloading 방법

void* operator new  ( std::size_t count )
{
	return malloc(count);
}

void operator delete  ( void* ptr )
{
	free(ptr)
}

 

이러한 사용방법들은 referece site 를 뒤져보면 아주 간단하게 알 수 있다.

 

3. 활용 방안

이 part 가 중요하다고 생각한다. 사실 실제 개발 할 때는 저것을 아는것도 중요하지만 어떻게 활용하느냐가 더 중요하기 때문이다. 

 

2가지 정도의 활용방안을 제시한다.

 

 1) 개선된 할당자의 사용.

우리들은 많은 할당자 library 를 알고 있다. tcmalloc 이라던가 boost 에 있는 memory pool 이라던가 등등
tcmalloc 이야 library link 만 해주면 알아서 다 해주지만 library 에서 제공되는 function 을 직접 사용해야하는 경우가 있다.  이경우 위 allocate / deallocate 에 구현만 해주면 사용이 가능하다.

 

간략한 개선된 할당자 소개 

tcmalloc : https://github.com/google/tcmalloc

멀티쓰레드 환경에서 alloc / free 에 성능이 아주 개선됨.

boost memory pool : https://www.boost.org/doc/libs/1_65_1/doc/html/interprocess/managed_memory_segments.html

fragmentation 이 발생하는 OS 에서 사용하기 좋음. 성능은 그닥..

intel IPP : https://software.intel.com/content/www/us/en/develop/tools/integrated-performance-primitives.html

intel chip 에서만 사용가능한 개선된 function 들. malloc / free 뿐만 아니라 memset / memcpy 등 유용하게 사용할 수 있는 것들이 많다. 

 

2) 메모리 사용량 수집.

긴말 필요없이 코드 먼저 보자.

std::atomic<std::size_t> g_totalSize = 0;
pointer allocate(size_type num, const void* = 0) 
{
	size_type size = num * sizeof(T) + sizeof(size_type);
	g_totalSize += size;
    	size_type* ret = ((size_type*)(::operator new(size));
        *ret = size;
   	return (pointer)++ret;
}

void deallocate(pointer p, size_type num) 
{
	size_type* size_ptr = p;
    	--size_ptr;
    	g_totalSize -= *size_ptr;
    	::operator delete((void*)size_ptr);
}

자 위와 같이 구현을 하면 g_totalSize 에 현재 사용중인 memory size 가 측정이 된다. 
그리고 new / delete 에도 적용이 가능하기에 container 뿐만 아니라 전체 사용 heap memory 도 실시간으로 알 수 있다. 

 

고객에서 발생한 문제가 직접 재현해보았을 때 재현이 되지 않는 경우가 많다.
여러가지 tool 을 이용하여 메모리 사용량을 측정할 순 있지만 재현이 되지 않으면 문제원인을 찾을 수 없다.
그래서 이러한 코딩을 해놓으면 log 를 통해 메모리관련 문제가 있었는지를 확인하는데 큰 도움을 준다. 

여러가지 활용방안이 더 있을 것이고 2번 항목을 기본으로 변형해가면 될 것이다.  ex) memory overrun 검출. 

 

위 방안을 알아두면 디버깅이 어려운 개발환경에서 도움이 되기에 알아두길 추천한다.

반응형