Pro*_*ala 3 c++ performance 3d-engine graph-traversal
这是关于良好做法的问题
考虑典型的情况,例如在3D引擎,物理引擎,有限元方法或经典分子动力学求解器中:您有各种类型的对象(例如顶点,边,面,有界实体体积)彼此交叉链接(例如顶点知道哪个边连接到它,反之亦然).对于这种引擎的使用性能和便利性,能够快速浏览这种连接的网络是至关重要的.
问题是:通过数组中的索引或指针指向链接对象是否更好?......特别是在表现方面
typedef index_t uint16_t;
class Vertex{
Vec3 pos;
#ifdef BY_POINTER
Edge* edges[nMaxEdgesPerVertex];
Face* faces[nMaxFacesPerVertex];
#else
index_t edges[nMaxEdgesPerVertex];
index_t faces[nMaxFacesPerVertex];
#endif
}
class Edge{
Vec3 direction;
double length;
#ifdef BY_POINTER
Vertex* verts[2];
Faces* faces[nMaxFacesPerEdge];
#else
index_t verts[2];
index_t faces[nMaxFacesPerEdge];
#endif
}
class Face{
Vec3 normal;
double isoVal; // Plane equation: normal.dot(test_point)==isoVal
#ifdef BY_POINTER
Vertex* verts[nMaxVertsPerFace];
Edge* edges[nMaxEdgesPerFace];
#else
index_t verts[nMaxVertsPerFace];
index_t edges[nMaxEdgesPerFace];
#endif
}
#ifndef BY_POINTER
// we can use other datastructure here, such as std:vector or even some HashMap
int nVerts,nEdges,nFaces;
Vertex verts[nMaxVerts];
Edge edges[nMaxEdges];
Vertex faces[nMaxFaces];
#endif
Run Code Online (Sandbox Code Playgroud)
指数优势:
uint8_t或uint16_t索引而不是32位或64位指针时,使用索引可以提高内存效率{0b000,0b001,0b010,0b011,0b100,0b101,0b110,0b111}).这些信息在指针中不可见指针的优点:
new Vertex().为了提高性能,重要的是您可以按照热路径中常见的遍历顺序读取“下一个”元素的速度。
例如,如果您有一系列代表某个路径的边,则希望将它们按new连接顺序连续地存储在内存中(而不是用于每个边)。
对于这种情况(形成路径的边缘),很明显,您不需要指针,也不需要索引。连接由存储位置暗示,因此您只需要指向第一个边缘(最后一个边缘)的指针即可(即,您可以将整个路径存储在中std::vector<Edge>)。
第二个示例说明了我们可以利用的领域知识:假设我们有一个游戏最多支持8个玩家,并且想存储“谁访问了路径中的每个边缘”。同样,我们不需要指针或索引来引用这8个播放器。取而代之的是,我们可以简单地uint8_t在每个内部存储一个内部Edge并将这些位用作每个玩家的标志。是的,这是底层的敲打,但是一旦有了,它就能为我们提供紧凑的存储和高效的查找Edge*。但是,如果我们需要从播放器到Edges 在另一个方向进行查找,则最有效的方法是uint32_t在每个播放器内部存储例如vector 并在Edge数组中建立索引。
但是,如果可以在路径的中间添加和删除边缘怎么办?好吧,我们可能想要一个链表。在这种情况下,我们应该使用侵入式链表,并Edge在池中分配。完成此操作后,我们可以将指向Edges的指针存储在每个玩家对象中,并且它们永远不会更改或不需要更新。我们使用侵入式链表时要了解,an Edge只是单个路径的一部分,因此,链表指针的外部存储将很浪费(std::list需要存储指向每个对象的指针;侵入式列表不会)。
因此,必须根据我们事先发现的领域知识,单独考虑每种情况。指针和索引都不应该是第一种方法。
当我们使用uint8_t或uint16_t作为索引而不是32位或64位指针时,使用索引可以提高内存效率
真正.具有小的表示减少了结构的总大小,减少了遍历它时的高速缓存未命中.
索引可以携带一些比特编码的一些附加信息(例如关于边缘的方向);
真正.
我们不需要关心存储对象的数组(或其他数据结构).可以通过新的Vertex()在堆上动态分配对象.
这正是你不想做的,谈到表演.您希望确保Vertex全部打包,以避免不必要的缓存丢失.在这种情况下,数组可以避免错误的诱惑.您还希望至少尽可能多地按顺序访问它们,以最大限度地减少缓存未命中.
数据结构的打包量,小数量和按顺序访问的数量实际上是驱动性能的因素.
可能更快(?)因为它不需要添加数组的基址(?).但就内存延迟而言,这可能是微不足道的(?)
可能微不足道.可能取决于特定的硬件和/或编译器.
索引的另一个缺失优势:重新分配时更容易管理.考虑一个可以增长的结构,如下所示:
struct VertexList
{
std::vector<Vertex> vertices;
Vertex *start; // you can still access using vector if you prefer; start = &vertices[0];
}
Run Code Online (Sandbox Code Playgroud)
如果使用指针引用给定顶点,并且发生重新分配,则最终会出现无效指针.
| 归档时间: |
|
| 查看次数: |
827 次 |
| 最近记录: |