C++ vector的自定义分配器效率极低<;char>;

C++ vector的自定义分配器效率极低<;char>;,c++,c++11,allocator,C++,C++11,Allocator,我想在下面的自定义分配器中使用一个向量,其中construct()和destroy()的主体为空: struct MyAllocator : public std::allocator<char> { typedef allocator<char> Alloc; //void destroy(Alloc::pointer p) {} // pre-c+11 //void construct(Alloc::pointer p, Alloc::cons

我想在下面的自定义分配器中使用一个向量,其中
construct()
destroy()
的主体为空:

struct MyAllocator : public std::allocator<char> {
    typedef allocator<char> Alloc;
    //void destroy(Alloc::pointer p) {} // pre-c+11
    //void construct(Alloc::pointer p, Alloc::const_reference val) {} // pre-c++11
    template< class U > void destroy(U* p) {}
    template< class U, class... Args > void construct(U* p, Args&&... args) {}
    template<typename U> struct rebind {typedef MyAllocator other;};
};
我注意到,通过改变大小,CPU消耗从30%增加到80%,尽管分配器有空的
construct()
destroy()
成员函数。因此,我希望对性能的影响非常小或根本没有影响(启用优化)。这一消费增量如何可能?第二个问题是:为什么在任何调整大小后读取内存时,我看到调整大小的内存中每个字符的值都是0(我希望有一些非零值,因为
construct()
什么都不做)

我的环境是g++4.7.0,启用了O3级优化。PC Intel双核,4GB可用内存。显然,对
构造的调用根本无法优化?

(下面是GManNickG和jonathanwakely的更正)

在C++11中,使用在中提出的标准后校正,
resize()
将使用自定义分配器构造添加的元素

在早期版本中,
resize()
value初始化添加的元素,这需要时间

这些初始化步骤与内存分配无关,而是分配内存后对内存所做的操作。价值初始化是不可避免的费用

考虑到当前编译器遵守C++11标准的情况,有必要查看您的头文件以了解使用的是哪种方法

值初始化有时是不必要和不方便的,但也保护了许多程序免受意外错误的影响。例如,有人可能认为他们可以调整
std::vector
的大小,使其具有100个“未初始化”字符串,然后在读取字符串之前开始赋值,但赋值运算符的先决条件是所更改的对象已正确构造。。。否则,它可能会找到一个垃圾指针并尝试
删除[]
它。只有小心地放置新的每个元素才能安全地构建它们。API设计在健壮性方面存在错误。

更新 这完全是重写。原始帖子/我的答案中有一个错误,使我对同一个分配器进行了两次基准测试。哎呀

嗯,我可以看到性能上的巨大差异。我做了下面的测试台,它采取了一些预防措施,以确保关键的东西没有完全优化。然后我验证(使用-O0-fno内联)分配器的
构造
析构函数
调用是否被调用了预期的次数(是):

我只能假设您所看到的是与标准库优化
char
的分配器操作相关的(它是POD类型)

当您使用时,计时变得更加遥远

struct NonTrivial
{
    NonTrivial() { _x = 42; }
    virtual ~NonTrivial() {}
    char _x;
};

typedef NonTrivial T;
在这种情况下,默认分配器需要超过2分钟(仍在运行)。
而“虚拟”MyAllocator花费约0.006秒。(注意这会调用引用未正确初始化的元素的未定义行为。)

您是否验证了正在调用您的
构造
函数?另外(或者可能是问题…)您不应该公开继承自
std::allocator
,它并不意味着它是一个基类。请提供一个我们可以复制、粘贴和运行的方法。@GManNickG,公开继承自
std::allocator
不是问题所在<代码>标准::分配器
是无状态且为空的,分配器由值使用,而不是由指向基的指针使用。在C++11中,最低的分配器要求非常简单,从
std::Allocator
继承的好处不大,但它对C++03的兼容性仍然很有用(大多数GCC的容器仍然使用C++03要求),它通过调用分配器的
构造来实现这一点,在C++98中,no?@GManNickG:,全功能原型是
void resize(size_type n,value_type val=value_type())。。。在函数内部,它不知道“val”是由调用方指定的还是拾取了默认值。。。它只是依次迭代每个元素。例如,在GCC 3.4.4的实现中,
resize()
调用
insert
哪个调用
\u M\u fill\u insert
哪个调用
copy\u backwards
然后
fill
…@GManNickG:C++11,从-如果n大于当前容器大小,则内容将通过在末尾插入所需数量的元素来扩展,以达到n的大小。如果指定了val,新元素将被初始化为val的副本,否则,它们将被值初始化。“该站点不正确。根据标准,当未指定值时(使用
resize
的单参数重载)然后,为了弥补缺少的元素,它们默认插入到容器中。如果容器使用默认分配器,这确实意味着值已初始化,但该站点未考虑自定义分配器。默认插入的正确一般形式是:
allocator\u traits::construct(m,p)
其中
m
是分配器(而
A
是它的类型),而
p
是将构造
T
的位置(
T
是值类型)。请参阅哪个更正了
resize
的规范以使用新的DefaultInsertable概念“与是否定义了自己的分配程序无关,所需的时间几乎完全相同。“-这重复了问题中的观察结果-即自定义分配器没有提高性能-但没有开始说明原因。
重新绑定
意味着
向量
将始终使用
分配器
,而不是您自己的类型,因此您正在对
std::分配器
std::分配器
进行基准测试,怪不得要花那么多时间
#include <vector>
#include <cstdlib>

template<typename T>
struct MyAllocator : public std::allocator<T> {
    typedef std::allocator<T> Alloc;
    //void destroy(Alloc::pointer p) {} // pre-c+11
    //void construct(Alloc::pointer p, Alloc::const_reference val) {} // pre-c++11
    template< class U > void destroy(U* p) {}
    template< class U, class... Args > void construct(U* p, Args&&... args) {}
    template<typename U> struct rebind {typedef MyAllocator other;};
};

int main()
{
    typedef char T;
#ifdef OWN_ALLOCATOR
    std::vector<T, MyAllocator<T> > v;
#else
    std::vector<T> v;
#endif
    volatile unsigned long long x = 0;
    v.reserve(1000000); // or more. Make sure there is always enough allocated memory
    for(auto i=0ul; i< 1<<18; i++) {
        v.resize(1000000);
        x += v[rand()%v.size()];//._x;
        v.clear(); // or v.resize(0);
    };
}
g++ -g -O3 -std=c++0x -I ~/custom/boost/ test.cpp -o test 

real    0m9.300s
user    0m9.289s
sys 0m0.000s

g++ -g -O3 -std=c++0x -DOWN_ALLOCATOR -I ~/custom/boost/ test.cpp -o test 

real    0m0.004s
user    0m0.000s
sys 0m0.000s
struct NonTrivial
{
    NonTrivial() { _x = 42; }
    virtual ~NonTrivial() {}
    char _x;
};

typedef NonTrivial T;