【问题标题】:Low performance from unordered_map when accessing elements in member template function in derived class访问派生类中成员模板函数中的元素时,unordered_map 的性能低下
【发布时间】:2015-10-29 09:47:45
【问题描述】:

我正在尝试在游戏引擎项目上实现基于组件的架构。每个 GameObject 都有一个 unordered_map,它包含一个指向 Component 基类的指针。此时,我只有一个组件派生类,即Transform 类。我想实现这个类似于 Unity 约定的基于组件的架构:我想通过调用像 GetComponent<Transform>() 这样的成员模板函数来获取游戏对象的组件。

这是标题:

Component.h

enum type{
    TRANSFORM   // more will be added later
};

class Component // base class
{
public:
    Component() : _owner(NULL) {}
    virtual ~Component(){}
    static type Type;

protected:
    GameObject* _owner;
};

Transform.h

class Transform : public Component
{
public:
    Transform();
    ~Transform();
    static type Type;

    void Rotate(float deg);

    // to be encapsulated later on
    Vector2D _position;
    float _rotation;
    Vector2D _scale;
};

GameObject.h

class GameObject
{
public:
    GameObject();
    ~GameObject();

    void Update();
    //and more member functions

    template<class T>
    T* GetComponent();
private:
    // some more private members
    unordered_map<type, Component*> _componentList; // only 1 component of each type
};

template<class T>
T* GameObject::GetComponent()
{       
    return static_cast<T*>(_componentList[T::Type]);
}

我的初始实现使用std::vector 来保持Component* 并且应用程序以60 fps 运行(我还有一个帧速率控制器,它只是将FPS 限制为60)。当我更改为 unordered_map 来访问这些组件指针时,性能下降到 15 FPS。

我只画了两个四边形,此时我每帧只调用了 6 次GetComponent&lt;Transform&gt;(),所以场景中没有发生太多事情。


我尝试了什么?

我尝试使用 const char*std::stringtype_info 和最后的 enum type 作为 unordered_map 的键值,但没有任何帮助:所有实现都让我达到了 15-16 FPS。

是什么导致了这个性能问题?如何隔离问题?

我希望我提供了足够的细节,如有需要,请随时索取更多代码

【问题讨论】:

  • 实体通常只有很少的组件,可能是 5 个或最多 30 个。对于 30 个元素,具有线性搜索的向量优于 hash_map。我会坚持使用向量。
  • 我对游戏编程一无所知,但你不能用总是 num_components 个元素来实例化一个指针数组吗?类型枚举值可以作为索引转换到数组中,如果你的组件很少,它可能不是很高的内存开销
  • 你还改变了什么?我不相信您更改了组件的存储容器。计算哈希值很简单。低于向 GPU 发送绘制指令的成本。
  • @RichardHodges 说真的,我几乎没有任何组件。我只是更改容器类型。如果我只是通过更改 GameObject.h 和 GameObject.cpp 再次将其恢复为 vector(在构造函数中,我只需将一个 Transform 组件添加到容器中://_componentList.push_back(new Transform());_componentList.emplace(Transform::Type, new Transform());
  • 如果你说的是真的,我想你需要分析它,看看发生了什么。错误不会在标准库中。另请注意,看在上帝的份上,请将指针存储在带有 unique_ptr 或 shared_ptr 的映射/向量中。看到容器中的原始指针让每个人都很难过:(

标签: c++ performance templates inheritance unordered-map


【解决方案1】:

免责声明:尽管下面的信息无论如何都应该成立,但鉴于在如此简单的情况下性能存在如此巨大的差异,第一次基本的健全性检查是首先确保构建优化已打开。就这样……

unordered_map 最终被设计为一个相当大的容器,预先分配了可能大量的存储桶。

请看这里: std::unordered_map very high memory usage

这里: How does C++ STL unordered_map resolve collisions?

虽然计算哈希索引是微不足道的,但如此小的unordered_maps 访问的内存量(以及它们之间的跨度)很容易变成缓存缺失瓶颈,因为访问频繁,例如从一个实体。

对于实体组件系统,通常不会有那么多组件与实体相关联——可能有几十个顶部,而且通常只有几个。因此,std::vector 实际上是一个更合适的结构,主要是在引用的局部性方面(每次从实体中获取组件接口时通常可以重复访问的小数组)。虽然较小,std::vector::operator[] 也是一个简单内联的函数。

如果你想在这里做得比std::vector 更好(但我只在分析并确定你需要它之​​后才推荐这个),前提是你可以推断出一些常见的案例上限N,用于组件的数量通常在实体中可用,这样的东西可能会更好:

struct ComponentList
{
    Component* components[N];
    Component** ptr;
    int num;
};

首先将ptr 设置为components,然后通过ptr 访问它们。插入新组件会增加num。当num &gt;= 4(罕见情况)时,更改ptr 以指向更大尺寸的动态分配块。销毁ComponentList 时,如果ptr != components 则释放动态分配的内存。如果您存储的元素少于N,这会浪费一点内存(尽管std::vector 通常也会以初始容量及其增长方式执行此操作),但它会将您的实体和提供的组件列表变成一个完全连续的结构除非num &gt; N。结果,您获得了最佳的参考位置,甚至可能比您开始时获得更好的结果(我假设从帧速率的显着下降中,您从实体中获取组件的频率非常高,这并不少见在 ECS 中)。

考虑到从实体访问组件接口的频率很高,而且经常在非常紧密的循环中,这可能是值得的。

尽管如此,考虑到数据的典型规模(实体中可用组件的数量),您最初选择的 std::vector 实际上是一个更好的选择。对于非常小的数据集,基本的线性时间顺序搜索通常优于更复杂的数据结构,因此您通常希望更多地关注内存/缓存效率。

我尝试使用 const char*、std::string、type_info,最后使用 enum 键入作为 unordered_map 的键值,但没有任何帮助:全部 实现让我达到了 15-16 FPS。

就这一点而言,对于键,您需要可以在恒定时间内比较的东西,例如整数值。在这种情况下可能很方便的一种可能性是一个实习字符串,它只存储一个 int 以在方便和性能之间取得平衡(允许客户端通过 string 构造它们,但在组件查找期间通过 int 比较它们) .

【讨论】:

  • 感谢您的详细回答。我忘了提供这个帖子的答案。问题是当我在更新函数for(auto obj : gameObjecList) 中循环遍历游戏对象时,我没有创建引用for(auto&amp; obj : gameObjecList) 而是复制,因此所有的ctor/dtor 调用和低fps。不过,我很高兴你参与其中。
  • 啊,我明白了,如果您在此之上加上复制开销以及相当大的初始 unordered_map 大小,那会使问题更加严重。但是,一旦达到足够大的规模,即使没有副本,您在 unordered_mapvector 之间看到的那种相对性能也应该会出现。如果可扩展性是重点,那么在此处关注对缓存友好的内存访问会有所帮助,如果您在此处使用较小的容器来获取组件,以及以更连续、连续的方式搜索它们的容器,您可以获得这些访问。
猜你喜欢
  • 1970-01-01
  • 2014-08-01
  • 1970-01-01
  • 2012-09-12
  • 2018-09-18
相关资源
最近更新 更多