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;