std::map访问速度太慢

std::map Access Speed Is Too Slow?

本文关键字:速度 访问 map std      更新时间:2023-10-16

我最近为我的游戏引擎实现了一个实体组件系统。每个实体都有这样的组件地图:

组件.h

enum COMPONENT_INFO {
    COMPONENT_POSITION = 0, 
    COMPONENT_PHYSICS,
    COMPONENT_RENDERABLE
 };

实体.h

class Entity {
public:
    std::bitset<NUM_COMPONENTS> componentBits;
    std::map<COMPONENT_INFO, Component*> components;
    ..
    ..
    Component *getComponent(COMPONENT_INFO inf) {
        return components[inf];
    }
 };

我的System类每帧更新每个实体,如下所示:

void update(Entity *e, float delta) { 
        PositionComponent *cmp=(PositionComponent*)e->getComponent(COMPONENT_INFO::COMPONENT_PHYSICS);
        //x += 1.0f;
        cmp->x += 1.0f;
    }

一切都按预期进行,但我在访问地图时遇到了巨大的性能问题。当我创建10000个实体并在其中迭代时(系统实际上在其中迭代),我得到了80 FPS的蓝色空白系统(没有视觉效果,只有纯屏幕)。但当我注释掉访问部分并只使用x += 1.0f;时,FPS会逐渐增加到1000。比如:

void update(Entity *e, float delta) { 
        x += 1.0f; //btw the system has a local x value.
    }

因此,问题仅在于通过映射访问组件。我还能在这样的系统上使用什么?或者我在访问地图时做错了什么?

重要编辑:这只是一个试驾,我的意思是每个实体可能有很多组件,而不仅仅是3个。我写这些只是为了测试。

由于每个实体的组件数量似乎是固定的,我会放弃映射,并使用entity中的成员变量来保存每个组件以及getter。这也将避免您的更新功能中的转换:

class Entity {
private:
    PositionComponent* m_positionComponent;
    PhysicsComponent* m_physicsComponent;
    RenderableComponent* m_renderableComponent;
public:
    // initialize the components in the constructor
    PositionComponent* getPositionComponent() {
        return m_positionComponent;
    }
    // more getters for the other components
}

或者,如果你想拥有更多的组件类型并保持设计的灵活性,你可能想将它们存储在一个向量中,使用枚举值对其进行索引。这将完全避免映射查找,并为你提供更好的性能。

由于COMPONENT_INFO的最高数量是固定的,您可以使用array<Component*>(或vector<>,并使用例如myArray[COMPONENT_INFO::COMPONENT_PHYSICS]进行寻址。不过,您可能必须检查nullptr。

首先,方法getComponent使用运算符[],它不仅检索具有给定键的实例,而且如果不存在,则插入一个元素。

http://www.cplusplus.com/reference/map/map/operator%5B%5D/

在给定的实现中,您将自己限制为实体中每种类型的一个组件,这可能不是最佳选择。例如,您可能希望为一个实体提供一些可渲染组件,以执行各种操作,如粒子、网格渲染等。

Map不能保证对象按顺序放置在内存中,这可能会在逐个处理时导致缓存未命中。例如,使用std::vector并将COMPONENT_INFO保留在组件对象本身中会更有效。

另一种解决方案是向系统类中添加方法,这些方法将创建特定类型的组件,在这样做的同时,对每个组件实例进行一些装订。然后,系统可以保留每种组件类型的矢量,并对这些矢量进行批处理。

希望这会有所帮助。

编辑:

其想法是,当您添加/创建新的碰撞组件时,组件本身会在系统中注册,提供类型和指向自身的指针。渲染图也是如此。您可以为系统中的每个组件类型提供矢量映射。当你需要解决collsion时,你只需要检索所有的collidables(对向量的引用)。您进行一次查找,即log(n),然后在所有collidables上迭代(由于位于向量中,指针在内存中按顺序排列,使其对缓存更友好)。

您还测试发布版本中的性能吗?