我想在下面使用带有自定义分配器的向量,其中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;};
};
现在由于我在another question中指定的原因,vector
必须在循环中多次调整大小。为了简化我的性能测试,我做了一个非常简单的循环,如下所示:
std::vector<char, MyAllocator> v;
v.reserve(1000000); // or more. Make sure there is always enough allocated memory
while (true) {
v.resize(1000000);
// sleep for 10 ms
v.clear(); // or v.resize(0);
};
我注意到,尽管分配器具有空construct()
和destroy()
成员函数,但是将CPU消耗的大小从30%增加到80%。因此,我本来期望对性能产生非常小的影响或没有影响(启用优化)。消费增量如何可能?第二个问题是:为什么在调整大小后读取内存时,我看到调整大小的内存中每个char的值为0(我会期望一些非零值,因为constuct()
什么都不做)?
我的环境是g ++ 4.7.0,启用了-O3级优化。 PC Intel双核,4GB可用内存。显然,对construct
的来电根本无法优化?
答案 0 :(得分:2)
这是一个完整的重写。原帖/我的答案中有一个错误,这使我对同一个分配器进行了两次基准测试。糟糕。
嗯,我可以看到性能上的巨大差异。我制作了以下试验台,它采取了一些预防措施,以确保关键的东西没有完全优化。然后我验证了(使用-O0 -fno-inline)分配器的construct
和destruct
调用被调用了预期的次数(是):
#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
我只能假设您所看到的内容与char
的标准库优化分配器操作有关(它是POD类型)。
使用
时,时间间隔会变得更远struct NonTrivial
{
NonTrivial() { _x = 42; }
virtual ~NonTrivial() {}
char _x;
};
typedef NonTrivial T;
在这种情况下,默认分配器需要超过2分钟(仍在运行)。 而'虚拟'MyAllocator花费约0.006秒。 (注意,这会调用未定义的行为,引用尚未正确初始化的元素。)
答案 1 :(得分:0)
(感谢GManNickG和Jonathan Wakely在下面进行更正)
在C ++ 11中,http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3346.pdf提出了标准后修正,resize()
将使用自定义分配器构建添加的元素。
在早期版本中,resize()
值会初始化添加的元素,这需要时间。
这些初始化步骤与内存分配无关,它是在分配后对内存所做的操作。价值初始化是不可避免的费用。
鉴于当前编译器中符合C ++ 11标准的状态,值得查看标题以查看正在使用的方法。
值初始化有时不必要且不方便,但也保护了很多程序免受意外错误的影响。例如,有人可能认为他们可以调整std::vector<std::string>
的大小,使其具有100个“未初始化”字符串,然后在读取之前开始分配它们,但是赋值运算符的先决条件是正在构造的对象已被正确构造...否则它可能会找到垃圾指针并尝试delete[]
它。只有仔细放置new
- 每个元素才能安全地构建它们。 API设计在稳健性方面存在错误。