是否有任何文档比较/对比C ++标准库的实现?[关闭]


16

(这本身不是游戏编程,但是我确定如果我这样问我,即使历史告诉我们每个大型游戏最终都担心这些事情,我还是被告知不要过早优化。)

是否有文档可以总结不同C ++标准库实现之间的性能差异,尤其是内存使用情况?一些实现的细节受到NDA的保护,但是即使是STLport与libstdc ++,libc ++与MSVC / Dinkumware(相对于EASTL)之间的比较似乎也非常有用。

我特别在寻找以下问题的答案:

  • 标准容器有多少内存开销?
  • 什么容器(如果有的话)仅通过声明进行动态分配?
  • std :: string是否在写时复制?短字符串优化?绳索?
  • std :: deque使用环形缓冲区还是废话?

我的印象deque是总是在STL中使用向量来实现。
四分

@Tetrad:直到几周前,我也是,但后来我读到它通常是用绳状结构实现的-这似乎就是STLport中的内容。

STL具有一个开放的工作草稿,可用于查找有关各种数据结构(顺序的和关联的),算法和实现的帮助程序类的信息。但是,内存开销似乎是实现特定的,而不是规范定义的。
托马斯·罗素

3
@Duck:游戏开发是我唯一知道的地方,它定期使用高级C ++功能,但由于它运行在低内存,无虚拟内存的系统上,因此需要精心跟踪内存分配。SO的每个答案都是“不要过早优化,STL很好,请使用它!” -到目前为止,这里有50%的答案是-但是Maik的测试清楚地表明了希望使用std :: map的游戏的主要关注点,而Tetrad对常见的std :: deque实现也感到困惑和困惑。

2
@Joe Wreschnig我真的不想投票结束,因为我对这个结果很感兴趣。:p
共产党鸭子

Answers:


6

如果找不到这样的比较表,则可以选择将自己的分配器注入到相关的STL类中,并添加一些日志记录。

我测试的实现(VC 8.0)仅通过声明字符串/向量/双端队列不使用任何内存分配,但是它确实列出并映射了。该字符串具有短字符串优化功能,因为添加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已通过测试,下面是比较(包括在测试中:字符串,向量,双端队列,列表,映射)

                    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输出字符串/向量/双端队列/列表/映射:

===============================================
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 2011年

“开销”是指结构及其所有关联迭代器的空实例的大小,以及更复杂的实例如何处理分配-例如std :: list一次内部分配多个节点,还是我?支付每个节点的基本分配成本等?

1
问题不是“请进行比较”,而是“进行比较的资源在哪里”-我认为SO不是“维护”它的好地方。也许您应该开始将其发布在Google网站或Wiki之类的东西上。

1
@Joe现在好了,这里:p我对将其移至另一个站点不是很感兴趣,我只是对结果感兴趣。
Maik Semder 2011年

8

std::string不会在写入时进行复制。CoW曾经是一种优化,但是一旦有多个线程进入画面,它就不会过于悲观了-它可能会因大量因素而减慢代码速度。太糟糕了,以致C ++ 0x Standard积极禁止它作为实施策略。不仅如此,而且允许std::string抛出可变的迭代器和字符引用还意味着“写”为std::string几乎需要执行所有操作。

我认为,短字符串优化大约在6个字符左右,或者在该区域内。不允许使用绳索- std::string必须为绳索存储连续的内存c_str()功能。从技术上讲,您可以将连续的绳子和绳子放在同一个类中,但没人能做到。而且,据我所知,使绳索具有线程安全性是非常慢的,可能比CoW还要糟。

没有容器通过在现代STL中声明来进行内存分配。像list和map这样的基于节点的容器曾经这样做,但是现在它们具有嵌入式最终优化,不需要它。通常会执行一个称为“ swaptimization”的优化,在该优化中您与一个空容器交换。考虑:

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

当然,在C ++ 0x中这是多余的,但是在C ++ 03中,当它被普遍使用时,如果MahVariable在声明时分配内存,那么它将降低有效性。我知道这样一个事实,即它用于更快地重新分配容器,例如vector在MSVC9 STL中,这消除了复制元素的需要。

deque使用称为展开链接列表的内容。它基本上是一个数组列表,通常是固定大小的节点内。因此,对于大多数用途而言,它保留了两种数据结构的优点-连续访问和分期摊销的O(1)删除,并且能够添加到前端和后端,并且迭代器失效比vectordeque向量永远都无法实现,因为它的算法复杂性和迭代器无效保证。

关联多少内存开销?好吧,老实说,这是一个毫无价值的问题。STL容器被设计为高效的,如果您要复制其功能,则结果可能会变得更糟,或者再次出现在同一位置。通过了解它们的底层数据结构,您可以知道它们使用,提供或占用的内存开销,并且仅出于充分的理由(例如小字符串优化),它才会超过内存开销。


“很糟糕,C ++ 0x标准积极禁止将其作为实施策略。” 并且他们禁止使用它,因为以前的实现曾经使用过或试图使用它。您显然生活在一个每个人都一直在使用最新的最佳实现的STL的世界中。这个答案根本没有帮助。

我也很好奇,您认为std :: deque的哪些属性会阻止连续的基础存储-迭代器仅在开始/结束删除后才有效,而在中间或任何插入之后都无效,这可以通过向量轻松完成。而且使用循环缓冲区似乎可以满足所有算法上的保证-摊销O(1)在末尾插入和删除,O(n)在中间删除。

3
@乔:我认为自90年代末以来,《牛顿》一直是一件坏事。有使用它的字符串实现-特别是CString-,但这并不意味着std::string有一段时间了。您不必为此使用最新和最好的STL实现。msdn.microsoft.com/zh-cn/library/22a9t119.aspx说:“如果在前面插入元素,则所有引用均保持有效”。不确定您打算如何使用循环缓冲区实现该目标,因为当缓冲区满时您将需要调整大小。
DeadMG


我当然不会捍卫COW作为一种实施技术,但是对于在被认定为不良之后很长时间仍继续使用不良技术来实施软件,我也不幼稚。例如,Maik的上述测试揭示了确实在声明时分配的现代stdlib。感谢您提供有关双端队列引用有效性的指示。(对于nitpick,向量可以满足关于迭代器失效和算法复杂性的所有保证;该要求都不是。)如果有的话,我认为这是对像我的问题所要求的文档的进一步需求。

2

问题不是“请进行比较”,而是“进行比较的资源在哪里”

如果这确实是您的问题(最确定的不是您在实际问题文本中说的,该问题以4个问题结尾,没有一个问您在哪里可以找到资源),那么答案很简单:

没有一个。

大多数C ++程序员不必太在意标准库结构的开销,它们的缓存性能(无论如何,它们都高度依赖于编译器)或此类事情。更不用说,您通常不会选择标准库的实现。您使用编译器附带的功能。因此,即使它做了一些不愉快的事情,替代方案的选择也是有限的。

当然,有些程序员会关心这种事情。但是很久以前,他们都使用标准库发誓。

因此,您只有一群根本不在乎的程序员。还有另一组程序员会关心他们是否在使用它,但是由于他们没有使用它,所以他们不在乎。由于没有人关心它,因此没有关于这种事情的真实信息。到处都有非正式的信息补丁(有效的C ++有一段关于std :: string实现以及它们之间的巨大差异的部分),但是没有什么全面的。当然也没有什么是最新的。


投机的答案。+1可能是正确的,-1则无法证明这一点。
deceleratedcaviar

过去,我已经看到了许多非常好的和详细的比较,但是它们都已经过时了。如今,采取举动之前的任何事情都已无关紧要。
彼得
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.