前段时间我编写了一个2D图形和物理引擎来进行游戏,现在我回过头来给它一些性能优化(仅用于训练)。
目前我正在考虑对存储和处理数据的方式进行一些优化。
基本上,我正在考虑三种方法,并希望对它们有一些建议(或者甚至是更好的主意)。
存储的数据通过多个线程非常频繁地访问。
我做了一个非常简单(不工作!)的代码示例来说明我的三种方法的执行是如何工作的。
这些是我用于此示例的类和结构:
发生碰撞的方向的枚举
enum direction
{
up,
down,
left,
right,
none //No collision
};
一个存储物理属性的类,它具有计算速度等简单的逻辑。
class PhysicProperties
{
public:
void calculateSpeedFromCollision(direction dir)
{
//Threadsafe!
//Some calculations
}
private:
float m_Velocity;
float m_Acceleration;
float m_Mass;
//Some other stuff
};
一个存储碰撞属性的类,它可以确定两个类是否相互碰撞
class CollisionProperties
{
public:
void checkCollision(Actor& actor)
{
bool collision = false;
//check collision
if(!collision)
{
m_CollisionDir = none;
}
}
direction getDirOfLastCollision()
{
//Threadsafe!
return m_CollisionDir;
}
private:
direction m_CollisionDir;
std::vector<std::pair<int, int> > m_CollisionBody;
//Some other stuff
};
包含Physic / CollisionProperties的actor类,以及GraphicProperties之类的东西等。
class Actor
{
public:
void processCollision()
{
//Threadsafe!
/*This function takes somewhat between 3 and 5 times the num of cpu cycles I need to
create a task + add task to pool + pool latency*/
direction dir = m_Collision.getDirOfLastCollision(); //if no collision happened, dir = none
if (dir != none)
{
m_Physics.calculateSpeedFromCollision(dir);
}
}
private:
PhysicProperties m_Physics;
CollisionProperties m_Collision;
};
好了,我正在考虑的3种方法:
在第一个方法中,Actors存储在一个向量中,每个物理计算有一个任务:
void doPhysic()
{
std::for_each(actors.begin(), actors.end(), [](Actor& actor)->void {m_Pool.run(actor, actor.processCollision); });
}
在第二种方法中,数据存储在一个向量中,但向量在n个线程之间分割。此示例非常静态(为简单起见)。我当然会把它编码得更加动态。
void doPhysic()
{
std::vector<Actor>::iterator begin1; // begin
std::vector<Actor>::iterator end1; // 1/4
std::vector<Actor>::iterator begin2; // 1/4
std::vector<Actor>::iterator end2; // 2/4
std::vector<Actor>::iterator begin3; // 2/4
std::vector<Actor>::iterator end3; // 3/4
std::vector<Actor>::iterator begin4; // 3/4
std::vector<Actor>::iterator end4; // 4/4
m_Pool.run(std::bind(std::for_each<std::vector<Actor>::iterator, void(*)(Actor&)>, begin1, end1, [](Actor& actor)->void { actor.processCollision(); }));
m_Pool.run(std::bind(std::for_each<std::vector<Actor>::iterator, void(*)(Actor&)>, begin2, end2, [](Actor& actor)->void { actor.processCollision(); }));
m_Pool.run(std::bind(std::for_each<std::vector<Actor>::iterator, void(*)(Actor&)>, begin3, end3, [](Actor& actor)->void { actor.processCollision(); }));
std::for_each(begin4, end4, [](Actor& actor)->void { actor.processCollision(); });
//Wait until 1-3 is done
}
在第三种方法中,我将数据存储在n个向量中(每个线程一个,并以有意义的方式排序)。
void doPhysic()
{
m_Pool.run(std::bind(std::for_each<std::vector<Actor>::iterator, void(*)(Actor&)>, actors1.begin(), actors1.end(), [](Actor& actor)->void { actor.processCollision(); }));
m_Pool.run(std::bind(std::for_each<std::vector<Actor>::iterator, void(*)(Actor&)>, actors2.begin(), actors2.end(), [](Actor& actor)->void { actor.processCollision(); }));
m_Pool.run(std::bind(std::for_each<std::vector<Actor>::iterator, void(*)(Actor&)>, actors3.begin(), actors3.end(), [](Actor& actor)->void { actor.processCollision(); }));
std::for_each(actors4.begin(), actors4.end(), [](Actor& actor)->void { actor.processCollision(); });
//Wait until 1-3 is done
}
我可以想象,3是最友好的缓存方式,因为不同的线程可以为自己预取数据并且不必共享很多,而且矢量不会变大,矢量甚至可能适合缓存作为一个整体。另一方面,如果我试图找到一个特定的演员,我会失去一些表现。
方法1不是非常缓存友好的,因为核心不应该能够预测下一个哪个actor,这意味着预取不会对此有效。
方法2应该是1和2之间的良好折衷,实际上是我赞成的。