[英]How to efficiently allocate space for vector of pointers to virtual base class in modern C++
I have following data model 我有以下数据模型
struct Base {
int x_;
int y_;
int z_;
virtual int getId() const;
virtual int getValue() const = 0;
virtual Base* create() const = 0;
bool operator<(const Base &other);
};
struct Derived0 : public Base {
virtual Derived0* create() const { return new Derived0(); };
virtual int getId() const;
virtual int getValue() const;
};
//...
struct DerivedN : public Base {
virtual DerivedN* create() const { return new DerivedN(); };
virtual int getId() const;
virtual int getValue() const;
};
and fill it in following way (simplified) 并按照以下方式填写(简体)
int n = 0;
std::shared_ptr<Base> templ[100];
templ[n++] = std::make_shared<Derived0>();
//...
templ[n++] = std::make_shared<DerivedN>();
std::vector<std::shared_ptr<Base>> b;
for (int i = 0; i < n; i++) {
while (...) { // hundreds of thousands iterations
std::shared_ptr<Base> ptr(templ[i]->create());
// previous call consumes most of the time
//...
b.push_back(ptr);
}
}
std::sort(b.begin(), b.end());
// ...
Since I need huge amount of derived objects I wonder if the initialization can be done more efficiently. 由于我需要大量的派生对象,所以我想知道初始化是否可以更有效地完成。 In showed case most of the time is spend by creating single shared pointers.
在所示的情况下,大多数时间都花在创建单个共享指针上。
I tried a way with preallocating an array of Base
objects (since all Derived
have same size), casting virtual type for each template and storing raw pointers to this array. 我尝试了一种预分配
Base
对象数组的方法(因为所有“ Derived
具有相同的大小),为每个模板强制转换虚拟类型并存储指向该数组的原始指针。 Not surprisingly such approach is many times faster. 毫不奇怪,这种方法快很多倍。 However is not clean,
vector
cannot be used and memory management is problematic. 但是,这并不干净,无法使用
vector
并且内存管理存在问题。
Can somebody give me an advice, how to do this efficiently in C++ way 有人可以给我建议吗,如何以C ++的方式有效地做到这一点
Create a variant style type eraser that makes everything look like Base
: 创建一个变体样式类型的橡皮擦,使一切看上去都像
Base
:
template<class T>struct tag{using type=T;};
template<class Base, class...Derived>
struct poly {
Base* get(){
return const_cast<Base*>( const_cast<poly const*>( this )->get() );
}
Base const* get()const{
if (!ops) return nullptr;
return ops->to_base(&raw);
}
Base* operator->(){ return get(); }
Base const* operator->()const{ return get(); }
Base& operator*(){ return *get(); }
Base const& operator*()const{ return *get(); }
explicit operator bool()const{ return get(); }
template<class T,class...Args,
class=std::enable_if<
/* T is one of Derived... */
>
>
void emplace(tag<T>,Args&&...args){
cleanup();
ops=&ops_for<T>();
new(&raw)T(std::forward<Args>(args)...);
}
poly& operator=(poly const& o){
if (this==&o)return *this;
cleanup();
if (!o->ops) return *this;
o->ops.copy_ctor( &raw, &o.raw );
ops=o->ops;
return *this;
}
poly& operator=(poly&&o){
if (this==&o)return *this;
cleanup();
if (!o->ops) return *this;
o->ops.move_ctor( &raw, &o.raw );
ops=o->ops;
return *this;
}
poly(poly const& o){
if (!o->ops)return;
o->ops.copy_ctor(&raw,&o.raw);
ops=o->ops;
}
poly(poly&& o){
if (!o->ops)return;
o->ops.move_ctor(&raw,&o.raw);
ops=o->ops;
}
private:
void cleanup(){
if (ops) ops->dtor(&raw);
ops=nullptr;
}
struct erase_ops{
void(*copy_ctor)(void*lhs,void const*rhs);
void(*move_ctor)(void*lhs,void*rhs);
void(*dtor)(void*ptr);
Base const*(*to_base)(void const*ptr);
};
template<class D>
static erase_ops const& ops_for(){
static erase_ops r={
// ...
};
return r;
};
erase_ops const* ops=nullptr; // = &ops_for<Derived1>(); etc
std::aligned_storage< /* size and alignment info */ > raw;
};
implementation left out, am on phone. 实施遗漏,正在打电话。
Once you have above, you can create a vector of poly<Base, Derived1, Derived2, ....
. 完成上述操作后,您可以创建
poly<Base, Derived1, Derived2, ....
的向量。 The cost is one extra pointer per instance. 成本是每个实例增加一个指针。
Now at this point we have already duplicayed most of virtual dispatch, so we could just include in the type erase the remaining operations on DerivedN
that are implemented as virtual methods and shave off another pointer's cost. 现在,我们已经复制了大多数虚拟调度,因此我们可以在类型中包括对
DerivedN
的其余操作(作为虚拟方法实现)并减少其他指针的开销。 If Base
is modestly bigger, I would not bother. 如果
Base
稍大一些,我就不会打扰。
C++ loves value types. C ++喜欢值类型。 Give it what it wants.
给它想要的东西。
It seems to me that a lot of your performance issues could be solved by using std::unique_ptr
and reserving some std::vector
memory in advance. 在我看来,您可以通过使用
std::unique_ptr
并预先保留一些std::vector
内存来解决许多性能问题。
std::shared_ptr<Base> ptr(templ[i]->create());
The above line involves dynamically allocating memory for both the derived type and the std::shared_ptr
control block. 上一行涉及为派生类型和
std::shared_ptr
控制块动态分配内存。 If you don't have shared ownership semantics, then using std::unique_ptr
instead will eliminate the need for one of those allocations. 如果您没有共享所有权语义,那么使用
std::unique_ptr
可以消除对这些分配之一的需求。
b.push_back(ptr);
When you do the above enough times, the vector will run out of memory it has allocated for you and try and allocate some more. 当您执行以上足够的时间时,向量将耗尽它为您分配的内存,并尝试分配更多的空间。
std::vector
is designed in such a way that this has amortized constant time complexity, but anything we can do to mitigate that, especially with huge vectors, will save time. std::vector
的设计方式可以分摊恒定的时间复杂度,但是我们可以采取任何措施来减轻这种情况,尤其是使用巨大的vector时,可以节省时间。
Your new code might look something like: 您的新代码可能类似于:
std::vector<std::unique_ptr<Base>> b;
b.reserve(n * /*number of iterations*/);
for (int i = 0; i < n; i++) {
while (...) { // hundreds of thousands iterations
std::unique_ptr<Base> ptr(templ[i]->create());
//...
b.push_back(ptr);
}
}
As an aside, you could limit code duplication for your prototype array creation by doing something like this: 顺便说一句,您可以通过执行以下操作来限制原型数组创建中的代码重复:
template <class Base, class... Derived, std::size_t... Idx>
auto arrayOfUniqueDerived (std::index_sequence<Idx...>)
{
std::array<std::unique_ptr<Base>, sizeof...(Derived)> arr;
(void) std::initializer_list<int> { (arr[Idx] = std::make_unique<Derived>(), 0)... };
return arr;
}
template <class Base, class... Derived>
auto arrayOfUniqueDerived ()
{
return arrayOfUniqueDerived<Base,Derived...>(std::index_sequence_for<Derived...>{});
}
Then use it like: 然后像这样使用它:
std::array<std::unique_ptr<Base>,3> templ =
arrayOfUniqueDerived<Base,Derived0,Derived1,Derived2>();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.