这是关于良好做法
的问题考虑典型情况,例如:在3D引擎,物理引擎,Finite element method或classical molecular dynamics求解器中:你有各种类型的对象(例如顶点,边,面,有界实体体积)彼此交叉链接(例如顶点知道)哪个边连接到它,反之亦然)。为了使用这种引擎的性能和便利性,能够快速浏览这种连接的网络是至关重要的。
问题是:通过数组中的索引或指针指向链接对象是否更好? ...尤其是性能方面
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
索引的优点:
uint8_t
或uint16_t
作为索引而不是32位或64位指针时,使用索引可以提高内存效率{0b000,0b001,0b010,0b011,0b100,0b101,0b110,0b111}
)。此信息在指针指针的优点:
new Vertex()
简单地在堆上动态分配对象。答案 0 :(得分:5)
为了表现,重要的是你能够快速阅读" next"任何遍历顺序中的元素通常都是在热路径中完成的。
例如,如果您有一系列表示某个路径的边,您可能希望它们连续存储在内存中(不是每个都使用new
),按照它们的连接顺序。
对于这种情况(形成路径的边缘),很明显你不需要指针,而且你也不需要索引。连接由存储位置隐含,因此您只需要指向第一个也可能是最后一个边的指针(即您可以将整个路径存储在std::vector<Edge>
中)。
第二个例子说明了我们可以利用的领域知识:想象一下,我们有一个游戏支持多达8个玩家,并希望存储&#34;谁已经访问了路径中的每个边缘。&#34;同样,我们不需要指针或索引来引用8个玩家。相反,我们可以简单地在每个uint8_t
中存储Edge
,并将这些位用作每个玩家的标记。是的,这是低级别的敲击,但是一旦我们有Edge*
,它就会为我们提供紧凑的存储和高效的查找。但是如果我们需要从另一个方向进行查找,从玩家到Edge
s,最有效的方法是存储,例如每个玩家内部的uint32_t
向量,并将其编入索引Edge
数组。
但是如果可以在路径中间添加和删除边缘怎么办?那么我们可能想要一个链表。在这种情况下,我们应该使用侵入式链接列表,并在池中分配Edge
。完成此操作后,我们可以在每个播放器对象中存储指向Edge
的指针,它们永远不会更改或需要更新。我们使用一个侵入式链表,并理解Edge
只是单个路径的一部分,因此链表指针的外部存储将是浪费的(std::list
需要存储指向每个对象的指针;侵入性列表不会。)
因此,必须单独考虑每个案例,并尽可能多地了解域名。指针和索引都不应该是第一种方法。
答案 1 :(得分:5)
当我们使用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];
}
如果使用指针引用给定顶点,并且发生重新分配,则最终会出现无效指针。