我正在写一些关于几何处理的代码,delaunay三角剖分,更具体地说,我需要它更快,所以我使用简单的原语数组作为数据结构来表示三角剖分信息,这是它的一个示例
private readonly float2[] points;
private readonly int[] pointsHalfEdgeStartCount;
private readonly int[] pointsIncomingHalfEdgeIndexes;
假设我想要快速迭代通过索引点p的所有传入半边,我只需使用预计算数组来实现:
int count = pointsHalfEdgeStartCount[p * 2 + 1];
for (int i = 0; i < count; i++)
{
var e = pointsIncomingHalfEdgeIndexes[pointsHalfEdgeStartCount[p * 2] + i]
}
// pointsHalfEdgeStartCount[p * 2] is the start index
这是足够快的,但感觉并不安全或非常清楚。所以我有了将我的索引包装到struct中的想法,以使其更清晰,同时保留性能,类似于:
public readonly struct Point
{
public readonly int index;
public readonly DelaunayTriangulation delaunay
public Point(int index, DelaunayTriangulation delaunay)
{
this.index = index;
this.delaunay = delaunay;
}
public int GetIncomingHalfEdgeCount() => delaunay.pointsEdgeStartCount[index * 2 + 1];
public HalfEdge GetIncomingHalfEdge(int i)
{
return new HalfEdge(
delaunay,
delaunay.pointsIncomingHalfEdgeIndexes[delaunay.pointsEdgeStartCount[index * 2] + i]
);
}
//... other methods
}
然后我就可以这样做了:
int count = p.GetIncomingHalfEdgeCount();
for (int i = 0; i < count; i++)
{
var e = p.GetIncomingHalfEdge(i);
}
然而,这有点扼杀了我的性能,在我做的基准测试中要慢得多(大约10倍),遍历所有的点和所有传入的半边。我猜是因为在每个point结构中存储对delaunay triangulaiton的引用是一种明显的浪费,并且减慢了所有涉及点的操作,因为有两倍的数据量要移动。
我可以让DelaunayTriangulation成为一个静态类,但由于其他原因它并不实用,所以我这样做了:
public readonly struct Point
{
public readonly int index;
public Point(int index) => this.index = index;
public int GetIncomingHalfEdgeCount(DelaunayTriangulation delaunay) => delaunay.pointsEdgeStartCount[index * 2 + 1];
public HalfEdge GetIncomingHalfEdge(DelaunayTriangulation delaunay, int i)
{
return new HalfEdge(
delaunay.pointsIncomingHalfEdgeIndexes[delaunay.pointsEdgeStartCount[index * 2] + i]
);
}
//... other methods
}
我可以这样做:
int count = p.GetIncomingHalfEdgeCount(delaunay);
for (int i = 0; i < count; i++)
{
var e = p.GetIncomingHalfEdge(delaunay, i);
}
它的速度要快得多,但仍然比使用简单int的第一种方法慢2.5倍。我想知道这是否可能是因为我在第一个方法中获得了int,而在其他方法中获得了HalfEdge结构(类似于Point结构的结构,只包含一个索引作为数据和几个方法),并且当我使用Point实例化一个新的HalfEdge结构时,普通的int和更快的结构之间的差异消失了。虽然我不确定为什么仍然如此costly.Weirder,但为了清楚起见,我尝试了在Delaunay类中编写方法而不是Point结构的选项:
// In the DelaunayTriangulation class:
public int GetPointIncomingHalfEdgeCount(Point p) => pointsEdgeStartCount[p.index * 2 + 1];
public HalfEdge GetPointIncomingHalfEdge(Point p, int i)
{
return new HalfEdge(
pointsIncomingHalfEdgeIndexes[pointsEdgeStartCount[p.index * 2] + i]
);
}
我是这样使用它的:
int count = delaunay.GetPointIncomingHalfEdgeCount(p);
for (int i = 0; i < count; i++)
{
var e = delaunay.GetPointIncomingHalfEdge(p, i);
}
它比之前的方法慢了3倍!我不知道为什么。
我尝试使用反汇编来查看生成了什么机器代码,但失败了(我正在使用Unity3D)。我是否注定要在数组中依赖普通的int和合理的变量命名,并放弃尝试进行一些编译时类型检查(这个int真的是点索引吗?)
我甚至没有提出其他问题,比如,当我尝试使用具有如下收益的IEnumerable类型时,为什么它会更慢:
public IEnumerable<int> GetPointIncomingHalfEdges(Point p)
{
int start = pointsEdgeStartCount[p.index * 2]; // this should be a slight optimization right ?
int count = pointsEdgeStartCount[p.index * 2 + 1];
for (int i = 0; i < count; i++)
{
yield pointsIncomingHalfEdgeIndexes[start + i];
}
}
发布于 2020-11-29 17:19:12
我已经为主动内联添加了一个编译器指令,它似乎弥补了时间上的差异!由于某些原因,编译器无法正确内联以下内容:
var e = delaunay.GetPointIncomingHalfEdge(p, i);
虽然它成功地通过
var e = p.GetIncomingHalfEdge(delaunay, i);
为什么?我不知道。然而,如果我能够看到代码是如何编译的,而我不知道如何做到这一点,那就容易多了。我会搜索它,也许会打开另一个问题,如果我找到更好的解释,我会回来的!
https://stackoverflow.com/questions/65061400
复制