更好的替代std :: vector <std :: unique_ptr <t>&gt;?</std :: unique_ptr <t>

时间:2013-12-23 15:41:33

标签: c++ performance c++11 vector containers

我正在寻找一个容器(针对游戏开发,尤其是实体管理),需要满足这些要求:

  1. 快速迭代
  2. 没有存储元素的副本
  3. 没有指向元素指针的失效
  4. 删除和插入元素
  5. 示例:

    Container<Entity> container;
    
    // This pointer will always point to the player
    Entity* player{new Entity};          
    container.add(player);               
    
    // Set some entities to "dead"
    for(auto& e : container) if(e->type == "Enemy") e->die(); 
    
    // Use erase-remove idiom on "dead" entities
    container.cleanup();                 
    
    // Player pointer is still valid
    player->doSomething();               
    

    到目前为止,我尝试了两种不同的容器类型:

    • std::vector<std::unique_ptr<T>>
      1. 缓存友好(快速迭代)
      2. 无副本(感谢std::unique_ptr
      3. 没有指针无效(感谢std::unique_ptr

    ...和...

    • std::list<T>
      1. 非缓存友好(缓慢的迭代)
      2. 无副本
      3. 没有指针无效

    即使看起来违反直觉,std::vector<std::unique_ptr<T>>的效果也比std::list<T> according to my benchmarks更高。

    (对于较大的类型,std::list<T>在插入过程中效果更佳,但std::vector<std::unique_ptr<T>>仍然获胜


    我想知道是否有更好的替代std::vector<std::unique_ptr<T>>

    理想情况下,替代方法应该是缓存友好型,以便快速迭代,并允许用户在添加/删除现有项之后引用相同的项(指针不应无效) )

2 个答案:

答案 0 :(得分:4)

通过性能测试,您正在做正确的事情。这是回答这个问题的唯一真实方式。

我唯一知道的可能更快就是创建一个缓冲区。然后为从缓冲区分配的vector<unique_ptr<T>, custom_allocator<unique_ptr<T>>>创建自定义分配器。

同样从同一个缓冲区分配对象(以便将unique_ptr指向缓冲区)。

要做到这一点,您必须知道上限,或者在超出限制时写入溢出逻辑。

让自定义分配器从缓冲区中间向上增长。

让unique_ptrs的分配从缓冲区的中间向下增长。

只要整个缓冲区适合缓存行,您就会尽可能快。这并非易事,您当前的解决方案可能已经足够好了。

答案 1 :(得分:1)

这听起来像是一个普通的std :: vector就是你想要的。

这是迄今为止最快的迭代,因为你在内存的连续部分进行一次扫描。你只能通过研究T的结构来做得更好。

可以使用O(1)中的push_back完成插入新元素。删除单个元素可以使用O(1)中的swap和pop_back的组合来完成。删除多个元素可以使用remove_if和erase的组合来完成。

您无法存储指针,因为它们将无效。但是,您可以做的是存储索引并使向量几乎全局可用。

使用索引而不是指针有两个优点:

  1. int使用4个字节,而指针使用8个字节。使用指针会自动导致更大的内存占用,从而导致更多的缓存未命中。我怀疑你将拥有超过2 ^ 32个实体,所以8个字节是过度的。
  2. 您可以使用数组结构而不是结构数组。
  3. 为了说明第2点,请看以下两个例子

    struct Unit{
        int type, health;
    };
    vector<Unit>units;
    for(int i=0; i<(int)units.size(); ++i)
        if(units[i].type == medic)
            units[i].health += 10;
    

    VS

    vector<int>type, health;
    for(int i=0; i<(int)type.size(); ++i)
        if(type[i] == medic)
            health[i] += 10;
    

    假设只有极少数的医务人员。在第一个代码片段中,由于类型和健康值在内存中相邻,因此整个内存必须通过缓存传输。在第二个示例中,只需要加载所有类型以及医务人员的少数健康值。总而言之,必须加载更少的内存,这会导致更快的迭代。