【问题标题】:Any better alternative to std::vector<std::unique_ptr<T>>?有什么比 std::vector<std::unique_ptr<T>> 更好的替代品吗?
【发布时间】:2014-01-11 20:34:46
【问题描述】:

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

  1. 快速迭代
  2. 没有存储元素的副本
  3. 指向元素的指针不会失效
  4. 移除和插入元素

例子:

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&lt;std::unique_ptr&lt;T&gt;&gt;
    1. 缓存友好(快速迭代)
    2. 没有副本(感谢std::unique_ptr
    3. 指针不会失效(感谢std::unique_ptr

...和...

  • std::list&lt;T&gt;
    1. 缓存不友好(迭代速度较慢)
    2. 无副本
    3. 指针不会失效

即使看起来有悖常理,std::vector&lt;std::unique_ptr&lt;T&gt;&gt; 也比 std::list&lt;T&gt; according to my benchmarks 更高效。

(对于更大的类型,std::list&lt;T&gt; 在插入期间性能更高,但std::vector&lt;std::unique_ptr&lt;T&gt;&gt; 仍然胜出)


我想知道是否有更好的替代 std::vector&lt;std::unique_ptr&lt;T&gt;&gt;

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

【问题讨论】:

  • 为什么你需要一个已经表现良好的vector的替代品?
  • 你试过std::deque吗?您将获得许多与 std::vector 相同的好处,但不需要大量连续内存来存储大量数据。
  • 你要更频繁地执行什么操作?
  • 我怀疑性能会更好,但你可以试试boost::ptr_vector&lt;T&gt;
  • @VittorioRomeo:如果您可以找到要创建的对象数量的上限,您可以使用某种Object Pool pattern 并避免拥有指针

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


【解决方案1】:

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

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

还从同一个缓冲区分配您的对象(这样 unique_ptr 指向缓冲区)。

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

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

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

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

【讨论】:

    【解决方案2】:

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

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

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

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

    使用索引代替指针还有两个优点:

    1. int 使用 4 个字节,而指针使用 8 个字节。使用指针会自动导致更大的内存占用,从而导致更多的缓存未命中。我怀疑你会有超过 2^32 个实体,所以 8 个字节是多余的。
    2. 您可以使用数组结构而不是结构数组。

    为了说明第 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;
    

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

    假设只有很少的医生。在第一个代码 sn-p 中,整个内存必须通过缓存进行管道传输,因为类型和健康值在内存中是相邻的。在第二个示例中,只需要加载所有类型以及医生的少数健康值。总之,需要加载的内存更少,从而导致更快的迭代。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-02-02
      • 2019-11-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-11
      • 2015-07-23
      • 2021-06-02
      相关资源
      最近更新 更多