简体   繁体   English

如何在现代C ++中有效地为虚拟基类的指针向量分配空间

[英]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 ++的方式有效地做到这一点

  • if all objects have same size? 如果所有对象的大小都相同?
  • if the size varies? 大小是否不同?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM