C ++ 표준 라이브러리 구현을 비교 / 대비하는 문서가 있습니까? [닫은]


16

(이것은 게임 프로그래밍 그 자체는 아니지만, 내가 이것을 물었을 때 나는 역사가 우리에게 모든 큰 게임 이이 일에 대해 걱정하게한다고 말하더라도 조기에 최적화하지 말라고 말할 것입니다.)

다른 C ++ 표준 라이브러리 구현 간의 성능 차이, 특히 메모리 사용량을 요약 한 문서가 있습니까? 일부 구현의 세부 사항은 NDA에 의해 보호되지만 STLport 대 libstdc ++ 대 libc ++ 대 MSVC / Dinkumware (vs. EASTL?) 간의 비교는 매우 유용한 것처럼 보입니다.

특히 나는 다음과 같은 질문에 대한 답을 찾고 있습니다.

  • 표준 컨테이너와 관련된 메모리 오버 헤드는 얼마입니까?
  • 어떤 컨테이너가 선언되어 단순히 동적 할당을 수행합니까?
  • std :: string은 쓰기시 복사합니까? 짧은 문자열 최적화? 로프?
  • std :: deque는 링 버퍼를 사용합니까?

나는 deque항상 STL에서 벡터로 구현 된 인상을 받았습니다 .
Tetrad

@Tetrad : 몇 주 전까지도 그랬지만, 종종 로프와 같은 구조로 구현 된 것을 읽었습니다. 이것이 STLport에있는 것 같습니다.

STL에는 open working-draft 가 있으며, 다양한 데이터 구조 (순차 및 연관), 알고리즘 및 구현 된 헬퍼 클래스에 관한 정보를 찾는 데 사용할 수 있습니다. 그러나 메모리 오버 헤드가 사양에 정의 된 것이 아니라 구현에 특정한 경우 인 것으로 보입니다.
토마스 러셀

3
@ 덕 : 게임 개발은 내가 알고있는 유일한 장소이며 정기적으로 높은 수준의 C ++ 기능을 사용하지만 메모리 부족 가상 메모리가없는 시스템에서 실행되므로 메모리 할당을 꼼꼼하게 추적해야합니다. SO에 대한 모든 단일 답변은 "조기 최적화하지 말고 STL은 괜찮습니다. 사용하십시오!" -지금까지 답변의 50 %는-그러나 Maik의 테스트는 std :: map을 사용하려는 게임에 대한 주요 관심사와 일반적인 std :: deque 구현에 대한 Tetrad의 혼란과 광산을 분명히 보여줍니다.

2
@Joe Wreschnig이 결과에 관심이 있기 때문에 마감 투표를하고 싶지 않습니다. : p
공산주의 오리

답변:


6

이러한 비교 차트를 찾지 못하면 대안으로 문제의 STL 클래스에 자체 할당자를 삽입하고 로깅을 추가하는 것입니다.

테스트 한 구현 (VC 8.0)은 문자열 / 벡터 / deque를 선언하는 것만으로 메모리 할당을 사용하지 않지만 목록과 매핑을 수행합니다. 3 개의 문자를 추가해도 할당이 트리거되지 않으므로 문자열의 문자열 최적화가 짧습니다. 출력은 코드 아래에 추가됩니다.

// basic allocator implementation used from here
// http://www.codeguru.com/cpp/cpp/cpp_mfc/stl/article.php/c4079

#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <deque>
#include <list>
#include <map>

template <class T> class my_allocator;

// specialize for void:
template <> 
class my_allocator<void> 
{
public:
    typedef void*       pointer;
    typedef const void* const_pointer;
    // reference to void members are impossible.
    typedef void value_type;
    template <class U> 
    struct rebind 
    { 
        typedef my_allocator<U> other; 
    };
};

#define LOG_ALLOC_SIZE(call, size)      std::cout << "  " << call << "  " << std::setw(2) << size << " byte" << std::endl

template <class T> 
class my_allocator 
{
public:
    typedef size_t    size_type;
    typedef ptrdiff_t difference_type;
    typedef T*        pointer;
    typedef const T*  const_pointer;
    typedef T&        reference;
    typedef const T&  const_reference;
    typedef T         value_type;
    template <class U> 
    struct rebind 
    { 
        typedef my_allocator<U> other; 
    };

    my_allocator() throw() : alloc() {}
    my_allocator(const my_allocator&b) throw() : alloc(b.alloc) {}

    template <class U> my_allocator(const my_allocator<U>&b) throw() : alloc(b.alloc) {}
    ~my_allocator() throw() {}

    pointer       address(reference x) const                    { return alloc.address(x); }
    const_pointer address(const_reference x) const              { return alloc.address(x); }

    pointer allocate(size_type s, 
               my_allocator<void>::const_pointer hint = 0)      { LOG_ALLOC_SIZE("my_allocator::allocate  ", s * sizeof(T)); return alloc.allocate(s, hint); }
    void deallocate(pointer p, size_type n)                     { LOG_ALLOC_SIZE("my_allocator::deallocate", n * sizeof(T)); alloc.deallocate(p, n); }

    size_type max_size() const throw()                          { return alloc.max_size(); }

    void construct(pointer p, const T& val)                     { alloc.construct(p, val); }
    void destroy(pointer p)                                     { alloc.destroy(p); }

    std::allocator<T> alloc;
};

int main(int argc, char *argv[])
{

    {
        typedef std::basic_string<char, std::char_traits<char>, my_allocator<char> > my_string;

        std::cout << "===============================================" << std::endl;
        std::cout << "my_string ctor start" << std::endl;
        my_string test;
        std::cout << "my_string ctor end" << std::endl;
        std::cout << "my_string add 3 chars" << std::endl;
        test = "abc";
        std::cout << "my_string add a huge number of chars chars" << std::endl;
        test += "d df uodfug ondusgp idugnösndögs ifdögsdoiug ösodifugnösdiuödofu odsugöodiu niu od unoudö n nodsu nosfdi un abc";
        std::cout << "my_string copy" << std::endl;
        my_string copy = test;
        std::cout << "my_string copy on write test" << std::endl;
        copy[3] = 'X';
        std::cout << "my_string dtors start" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "vector ctor start" << std::endl;
        std::vector<int, my_allocator<int> > v;
        std::cout << "vector ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            v.push_back(i);
        }
        std::cout << "vector dtor starts" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "deque ctor start" << std::endl;
        std::deque<int, my_allocator<int> > d;
        std::cout << "deque ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            std::cout << "deque insert start" << std::endl;
            d.push_back(i);
            std::cout << "deque insert end" << std::endl;
        }
        std::cout << "deque dtor starts" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "list ctor start" << std::endl;
        std::list<int, my_allocator<int> > l;
        std::cout << "list ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            std::cout << "list insert start" << std::endl;
            l.push_back(i);
            std::cout << "list insert end" << std::endl;
        }
        std::cout << "list dtor starts" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "map ctor start" << std::endl;
        std::map<int, float, std::less<int>, my_allocator<std::pair<const int, float> > > m;
        std::cout << "map ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            std::cout << "map insert start" << std::endl;
            std::pair<int, float> a(i, (float)i);
            m.insert(a);
            std::cout << "map insert end" << std::endl;
        }
        std::cout << "map dtor starts" << std::endl;
    }

    return 0;
}

지금까지 VC8과 STLPort 5.2를 테스트했지만 여기에 비교가 있습니다 (테스트에 포함됨 : string, vector, deque, list, map)

                    Allocation on declare   Overhead List Node      Overhead Map Node

VC8                 map, list               8 Byte                  16 Byte
STLPort 5.2 (VC8)   deque                   8 Byte                  16 Byte
Paulhodge's EASTL   (none)                  8 Byte                  16 Byte

VC8 출력 문자열 / 벡터 / deque / list / map :

===============================================
my_string ctor start
my_string ctor end
my_string add 3 chars
my_string add a huge number of chars chars
  my_allocator::allocate    128 byte
my_string copy
  my_allocator::allocate    128 byte
my_string copy on write test
my_string dtors start
  my_allocator::deallocate  128 byte
  my_allocator::deallocate  128 byte

===============================================
vector ctor start
vector ctor end
  my_allocator::allocate     4 byte
  my_allocator::allocate     8 byte
  my_allocator::deallocate   4 byte
  my_allocator::allocate    12 byte
  my_allocator::deallocate   8 byte
  my_allocator::allocate    16 byte
  my_allocator::deallocate  12 byte
  my_allocator::allocate    24 byte
  my_allocator::deallocate  16 byte
vector dtor starts
  my_allocator::deallocate  24 byte

===============================================
deque ctor start
deque ctor end
deque insert start
  my_allocator::allocate    32 byte
  my_allocator::allocate    16 byte
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
  my_allocator::allocate    16 byte
deque insert end
deque dtor starts
  my_allocator::deallocate  16 byte
  my_allocator::deallocate  16 byte
  my_allocator::deallocate  32 byte

===============================================
list ctor start
  my_allocator::allocate    12 byte
list ctor end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list dtor starts
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte

===============================================
map ctor start
  my_allocator::allocate    24 byte
map ctor end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map dtor starts
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte

STLPort 5.2. VC8로 컴파일 된 출력

===============================================
my_string ctor start
my_string ctor end
my_string add 3 chars
my_string add a huge number of chars chars
  my_allocator::allocate    115 byte
my_string copy
  my_allocator::allocate    115 byte
my_string copy on write test
my_string dtors start
  my_allocator::deallocate  115 byte
  my_allocator::deallocate  115 byte

===============================================
vector ctor start
vector ctor end
  my_allocator::allocate     4 byte
  my_allocator::deallocate   0 byte
  my_allocator::allocate     8 byte
  my_allocator::deallocate   4 byte
  my_allocator::allocate    16 byte
  my_allocator::deallocate   8 byte
  my_allocator::allocate    32 byte
  my_allocator::deallocate  16 byte
vector dtor starts
  my_allocator::deallocate  32 byte

===============================================
deque ctor start
  my_allocator::allocate    32 byte
  my_allocator::allocate    128 byte
deque ctor end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque dtor starts
  my_allocator::deallocate  128 byte
  my_allocator::deallocate  32 byte

===============================================
list ctor start
list ctor end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list dtor starts
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte

===============================================
map ctor start
map ctor end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map dtor starts
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte

EASTL 결과, 사용 가능한 대기열 없음

===============================================
my_string ctor start
my_string ctor end
my_string add 3 chars
  my_allocator::allocate     9 byte
my_string add a huge number of chars chars
  my_allocator::allocate    115 byte
  my_allocator::deallocate   9 byte
my_string copy
  my_allocator::allocate    115 byte
my_string copy on write test
my_string dtors start
  my_allocator::deallocate  115 byte
  my_allocator::deallocate  115 byte

===============================================
vector ctor start
vector ctor end
  my_allocator::allocate     4 byte
  my_allocator::allocate     8 byte
  my_allocator::deallocate   4 byte
  my_allocator::allocate    16 byte
  my_allocator::deallocate   8 byte
  my_allocator::allocate    32 byte
  my_allocator::deallocate  16 byte
vector dtor starts
  my_allocator::deallocate  32 byte

===============================================
list ctor start
list ctor end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list dtor starts
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte

===============================================
map ctor start
map ctor end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map dtor starts
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte

이는 기본 할당에 대한 세부 정보를 얻는 데 유용하지만 불행히도 오버 헤드 및 예상 캐시 성능에 대해서는 아무 것도 알려주지 않습니다.

@Joe 맞아, 모든 질문을 한 번에 해결하기는 어렵습니다. 나는 "오버 헤드"와 정확히 무엇을 의미하는지 확실하지 않은가? 오버 헤드로 메모리 소비를 의미한다고 생각했습니다.
Maik Semder

"오버 헤드"란 구조의 빈 인스턴스와 모든 관련 이터레이터의 크기와 더 복잡한 인스턴스가 할당을 처리하는 방법을 의미했습니다. 각 노드 등에 대한 기본 할당 비용을 지불합니까?

1
문제는 "이 비교를위한 리소스가있는 곳"과 같이 "이 비교를 수행하십시오"라는 것이 아닙니다. "SO를 유지하는"좋은 곳이라고 생각하지 않습니다. 아마도 당신은 구글 사이트 나 위키 등에서 던지기 시작해야 할 것입니다.

1
@Joe now now here : p 나는 다른 사이트로 옮기는 데 관심이 없지만 결과에 관심이있었습니다.
Maik Semder

8

std::string쓰기시 복사를하지 않습니다. CoW는 최적화 였지만 여러 스레드가 그림에 들어가 자마자 비관을 초월합니다. 엄청난 요인으로 인해 코드 속도가 느려질 수 있습니다. C ++ 0x Standard가이를 구현 전략으로 적극적으로 금지하는 것은 너무 나쁩니다. 그뿐만 아니라 std::string변경 가능한 반복자와 문자 참조를 제거 하는 것이 허용 된다는 것은 "쓰기" std::string가 거의 모든 작업 을 수반 한다는 것을 의미합니다 .

짧은 문자열 최적화는 약 6 자 또는 그 지역의 것입니다. 로프는 허용되지 않습니다 . 기능을 std::string위해 연속 메모리를 저장해야합니다 c_str(). 기술적으로, 당신은 같은 클래스에서 연속적인 줄과 줄을 둘 다 유지할 수 있었지만, 아무도 그렇게하지 않았습니다. 또한 내가 알고있는 로프에서 스레드를 조작하기에 안전한 스레드로 만드는 것은 CoW보다 훨씬 나쁘거나 나쁠 수 있습니다.

최신 STL에서 선언되어 메모리 할당을 수행하는 컨테이너는 없습니다. 목록 및 맵과 같은 노드 기반 컨테이너는 그렇게 했었지만 이제는 최종 최적화 기능이 내장되어 있으며 필요하지 않습니다. 빈 컨테이너로 교체하는 "스왑 최적화"라는 최적화를 수행하는 것이 일반적입니다. 치다:

std::vector<std::string> MahFunction();
int main() {
    std::vector<std::string> MahVariable;
    MahFunction().swap(MahVariable);
}

물론 C ++ 0x에서는 중복되지만 C ++ 03에서는 이것이 일반적으로 사용되었을 때 MahVariable이 선언시 메모리를 할당하면 효과가 감소합니다. vectorMSVC9 STL 과 같이 컨테이너를 더 빠르게 재 할당하는 데 사용 되어 요소를 복사 할 필요가 없다는 사실을 알고 있습니다.

deque롤링되지 않은 연결 목록이라고하는 것을 사용합니다. 기본적으로 배열의 목록이며 일반적으로 고정 크기 인 노드입니다. 따라서 대부분의 경우, 데이터 구조-연속 액세스 및 상각 된 O (1) 제거의 이점을 유지하며 앞뒤에 추가 할 수 있고 반복자 무효화보다 낫습니다 vector. deque알고리즘 복잡성과 반복자 무효화 보장으로 인해 벡터로 구현할 수 없습니다.

얼마나 많은 메모리 오버 헤드가 연관되어 있습니까? 솔직히, 그것은 물어볼 가치가없는 질문입니다. STL 컨테이너는 효율적으로 설계되었으며, 기능을 복제 할 경우 성능이 저하되거나 다시 동일한 지점에서 다시 작동하게됩니다. 기본 데이터 구조를 알면 사용, 제공 또는 사용하는 메모리 오버 헤드를 알 수 있으며 작은 문자열 최적화와 같은 적절한 이유보다 더 많은 것입니다.


"C ++ 0x 표준이이를 구현 전략으로 적극적으로 금지하는 것은 너무 나쁘다." 그리고 이전 구현에서 사용했거나 사용하려고 시도했기 때문에 금지했습니다. 모든 사람들이 항상 최적으로 구현 된 최신 STL을 사용하는 세상에 살고 있습니다. 이 답변은 전혀 도움이되지 않습니다.

또한 std :: deque의 속성이 인접한 기본 저장소를 방지한다고 생각합니다. 반복자는 시작 / 끝에서 제거 한 후에 만 ​​유효합니다. 중간 또는 삽입물이 아닌 벡터로 쉽게 수행 할 수 있습니다. 그리고 원형 버퍼를 사용하면 모든 알고리즘 보장을 충족하는 것으로 보입니다. 끝에서 O (1) 삽입 및 삭제, 중간에서 O (n) 삭제.

3
@Joe : 90 년대 후반부터 CoW가 나쁜 것으로 여겨졌습니다. CString을 사용하는 문자열 구현이 있지만 std::string시간이 걸린다는 의미는 아닙니다 . 이를 위해 최신 STL 구현을 사용하지 않아도됩니다. msdn.microsoft.com/ko-kr/library/22a9t119.aspx에 "요소가 맨 앞에 삽입되면 모든 참조는 유효합니다"라고 말합니다. 순환 버퍼가 가득 찼을 때 크기를 조정해야하기 때문에 순환 버퍼로 구현하려는 방법을 잘 모르겠습니다.
DeadMG


필자는 COW를 구현 기술로 방어하지는 않겠지 만, 소프트웨어가 빈약 한 것으로 식별 된 후에도 빈약 한 기술을 사용하여 소프트웨어가 얼마나 자주 구현되는지에 대해서는 순진하지 않습니다. 예를 들어, 위 Maik의 테스트는 선언에 할당하는 현대적인 stdlib를 보여줍니다. 참조 참조 유효성에 대한 포인터에 감사드립니다. (nitpick으로, 벡터는 반복자 무효화 및 알고리즘 복잡성에 대한 모든 보증을 충족시킬 수 있습니다. 그 요구 사항은 아닙니다.) 무엇이든, 나는 이것이 내 질문과 같은 문서가 더 필요하다고 생각합니다.

2

"이 비교를위한 자원은 어디에 있습니까?"

이것이 실제로 귀하의 질문 ( 실제 질문 텍스트에서 말한 것이 아니며 , 4 개의 질문으로 끝나고 어느 곳에서 리소스를 찾을 수 있는지 묻지 않은 질문)이라면 대답은 간단합니다.

없어요

C ++ 프로그래머의 대부분은 표준 라이브러리 구조의 오버 헤드 (인 그들의 캐시 성능에 대해 그렇게 많이 걱정하지 않아도 매우 컴파일러 의존 어쨌든), 또는 그런 종류의 물건을. 말할 것도없이, 일반적으로 표준 라이브러리 구현을 선택하지는 않습니다. 컴파일러와 함께 제공되는 것을 사용합니다. 따라서 불쾌한 일을하더라도 대안에 대한 옵션은 제한적입니다.

물론 이런 종류의 일을 걱정하는 프로그래머가 있습니다. 그러나 그들은 모두 오래 전에 표준 라이브러리를 사용하는 것을 맹세했습니다.

그래서 당신은 단순히 신경 쓰지 않는 프로그래머 그룹을 가지고 있습니다. 그리고 그것을 사용하고 있는지 걱정하는 또 다른 프로그래머 그룹은 그것을 사용하지 않기 때문에 신경 쓰지 않습니다. 아무도 신경 쓰지 않기 때문에 이런 종류의 것에 대한 실제 정보는 없습니다. 비공식적 인 정보 패치가 여기 저기 있습니다 (효과적인 C ++에는 std :: string 구현에 대한 섹션과 그 사이의 큰 차이점이 있습니다). 그리고 확실히 최신 정보는 없습니다.


추론 적 대답. 아마 사실이면 +1, 그것을 증명할 방법이 없으면 -1.
감속

나는 과거에 매우 훌륭하고 자세한 비교를 많이 보았지만 모두 구식입니다. 이동 도입 이전의 모든 것은 요즘 거의 관련이 없습니다.
Peter-Unban Robert Harvey
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.