boost make_shared with optimization 부스트 make_shared, 그리고 최적화 옵션 boost

Vㅔ리 어썸한 것이 있다. shared_ptr 과 함께 쓰면 아주 유용한 것이다. 바로 boost::make_shared 이다. 원문서에는 다음과 같이 설명한다.

Introduction

Consistent use of shared_ptr can eliminate the need to use an explicit delete, but alone it provides no support in avoiding explicit new. There have been repeated requests from users for a factory function that creates an object of a given type and returns a shared_ptr to it. Besides convenience and style, such a function is also exception safe and considerably faster because it can use a single allocation for both the object and its corresponding control block, eliminating a significant portion of shared_ptr's construction overhead. This eliminates one of the major efficiency complaints about shared_ptr.

The header file <boost/make_shared.hpp> provides a family of overloaded function templates, make_shared and allocate_shared, to address this need. make_shareduses the global operator new to allocate memory, whereas allocate_shared uses an user-supplied allocator, allowing finer control.

The rationale for choosing the name make_shared is that the expressionmake_shared<Widget>() can be read aloud and conveys the intended meaning.


됐고! 결론만 말하자면 기존의 new, malloc 의 동적으로 메모리를 할당할 때에 단일 메모리 구성이 아닌 뛰엄뛰엄(메모리 구조를 설명하자면 매우 길다. os 가 메모리를 사용할 때 단일 구조로 쓰지 않는다. 이건 다 알지? 기본 할당 단위가 있기 때문에 다음 메모리 블럭이 (예를들어 기본메모리 블럭의 크기가 8메가라고 하면) 6메가 밖에 되지 않으면 건너 뛴다. 그래서 듬성듬성 빈공간이 생긴다. ) 엄마손 파이처럼 빈공간이 생긴다. 이렇게 할당된 메모리 영역이 단일이 아니라 두개 이상이 되면 오버헤드가 걸리게 된다. 하지만 make_shared 의 경우에는 메모리를 할당할 때 단일 메모리 블럭상에 만들어 준다!!!!!!!!

// 2013.04.23 수정
이러한 현상을 메모리 단편화 라고 한다. 영어로는 fragmentation 이라고도 한다. 단편화가 심해지면 성능저하가 발생한다. 이러한 단편화를 줄이고자 만든 것이다. STL 의 경우에도 Memory Allocator 를 요구하지 않는데 이는 Basic Memory Allocator 의 경우 범용으로 사용되고 시간과 비용이 크게 발생하기 때문이다. 특정 클래스나 모듈의 경우 재정의한, 즉 operator new, operator new[] 를 사용하면 시간과 공간 비용을 크게 절약할 수 있다. 이런 concept 으로 나온것이 make_shared 이다.
operator new 와 allocator 는 구성방식이 다르다.

// 2013.04.23 수정 끝

그럼 엄청 빨라진다는 말이된다. 특히 shared_array 의 경우는 더할 것이다. read 로만 따진다면 vector 와 list 의 속도차이가 되지 않을까 예상해 본다. 그럼 실제로 그렇게 동작되는지 살펴보자.

#include <memory>
#include <string>
 
class Foo
{
public:
    typedef std::shared_ptr<Foo> Ptr;
 
    Foo()
    : a(42)
    , b(false)
    , c(12.234)
    , d("FooBarBaz")
    {}
 
private:
    int a;
    bool b;
    float c;
    std::string d;
};
 
const int loop_count = 100000000;
int main(int argc, char** argv)
{
    for (int i = 0; i < loop_count; i++)
    {
#ifdef USE_MAKE_SHARED
        Foo::Ptr p = std::make_shared<Foo>();
#else
        Foo::Ptr p = Foo::Ptr(new Foo);
#endif
    }
    return 0;
}


자 이상하게 make_shared 가 더 올래걸렸다. 이런 망할. 이유는 최적화 옵션을 지정하지 않았기 때문이다.
Speed comparison: allocating objects in C++std::...newg++16,00018,00020,00022,00024,000time (milliseconds)
최적화 옵션을 지정하고 테스트했을 때 속도다. 결론은 make_shared 는 최적화 옵션과 함께 사용해야 한다는 것이다. 단독으로 사용할 경우 독이된다. 마치 무조건 소멸자를 가상함수로 지정하는 것처럼 말이다.

Speed comparison: allocating objects in C++std::...newg++g++ -O25,00010,00015,00020,00025,000time (milliseconds)
또 다른 테스트 결과이다. 역시 최적화 옵션을 같이 써야 한다. 

Speed comparison: allocating objects in C++std::...newg++g++ -O2clang++clang++ -O25,00010,00015,00020,00025,000time (milliseconds)

Compilerstd::make_sharednew
g++2266417559
g++ -O277459483
clang++2249916429
clang++ -O277629585

사실 그렇게 큰 차이는 아니라고 생각할 수 있지만 이것이 array 가 된다면 무시할 것이 못된다.

allocate_shared 와 new [] 의 차이는 위 그래프보다 더욱 벌어질 것이다. allocate_shared, make_shared
는 동일하게 사용가능하다. 단지 네이밍을 구분하기 위한 것 뿐이다. 소스를 보면 알겠지만 둘 다
배열을 반환할 수 있다.


그냥 복잡하다 싶으면 무조건 make_shared 를 쓰면된다. 물론 최적화 옵션을 함께!



덧글

댓글 입력 영역