我知道如何找到以2D兴趣点(POI)为根的光线是否与给定的2D线段相交,以及给定光线如何有效地找到最接近具有多个线段的POI交叉点。
然而,我有几条(不是太多)光线根植于许多(我的意思是很多)POI和许多(我的意思是很多)细分市场,我需要找到最接近相应POI交叉点的每条光线的分段。
以下是我对此事的想法:将光线视为在相应POI的一侧以及在正确方向上非常远的另一侧的线段,然后运行sweep line algorithm以查找所有交叉点,然后为每个射线输出最接近POI的交叉点。这应该在O(N log N)中运行,因此有点好。
然而,这部分恰好是整个系统的瓶颈,而且我希望做得更好而不是有点好。特别是,因为确实存在许多对象需要进行排序我想使这个算法并行。不幸的是,扫描线算法似乎本质上是连续的,并且与之相关将会影响我的其他相当并行的系统的整体性能。
因此,问题是:您对上述问题的有效并行化有何建议?
此外,是否有任何已知的高度并行交叉检测算法能够利用CUDA并行水平?
答案 0 :(得分:1)
您可以尝试基于quadtree或其他一些空间索引的2d数据结构来执行算法。
这是一个想法:
在四叉树的每个叶子中,存储对与该正方形相交的任何片段的引用。首先,你的分辨率为零,即所有分段都在边界框节点中,根本不进行细分。
然后,对于每条光线,查看光线的原点并细分包含它的节点,直到节点仅与一个线段相交。如果在细分之后节点不与任何段相交,则通过跟随光线移动到下一个节点。如果节点与节点内的一个段相交,我们找到了第一个交叉点,并且可以沿着该光线停止搜索。如果光线不与节点内的线段相交,我们可以从搜索中修剪该线段,然后移动到光线相交的下一个节点。
通过并行执行沿每条射线的搜索,该算法显然可以并行运行。仍然会有共享的四叉树,但是当它变得细分时,线程很快就不会竞争访问。
运行时间:
运行时间有点难以分析,因为它取决于段和光线的2d结构。也许有人可以帮助你更好地分析运行时间,但这是我最好的镜头:
如果小距离d小于距离最近的非交叉段的任何光线的距离,并且小于同一光线上任意两个交叉点之间的距离,那么我们可以得到一个上限。然后我们可以将r = D / d称为问题的“分辨率”,其中D是边界框的维度。
运行时间将受N * r
限制但这是一个非常粗略的界限,因为它基本上假设每条射线都必须沿着整个长度分解为分辨率d。
让我们尝试改进这个界限。
对于任何光线,请考虑光线在其原点与其第一个交点之间的截面。对于沿光线部分的任何点,我们可以询问哪个非相交段最接近该点。然后,我们可以询问有多少不同的非交叉段最接近该光线的某个点。换句话说,如果我们制作了一个Voronoi diagram非交叉段,我们就会问这段光线相交多少个单元格。如果我们让p成为任何射线的这个问题的最大答案,那么我们可以获得更好的上界
N*p*log(r)
通过考虑沿光线搜索时所需的最大分辨率。
因此,如果p和r是常数,我们有一个线性时间算法!
不幸的是,我很确定p,N和r有一些关系可以防止p和r保持大致不变。粗略猜测,我会说r ~ sqrt(N)
,这意味着它会回到N log N
运行时间。
并行化很有趣。如果我们假设C核心,我们的搜索运行时间为N/C*p*log(r)
,但仍然存在划分子树的瓶颈。有log(r)
级别,每个细分必须在最多N个位置划分为此级别,因此需要N/C*log(r)
时间,这意味着总运行时间将为
(N/C)*p*log(r)
这基本上意味着它是完全可并行化的,假设C < Ñ
<强>结论强> 在我看来,作为单核算法,这可能不会那么好,但对于具有特殊结构和/或并行化的问题,它可能运行良好。我不建议尝试实现这个算法,除非你已经考虑了很多,并且你确定粗糙的边缘可以被平滑掉,并且我的分析没有出错。我还建议根据类似的概念搜索已发布的算法。
答案 1 :(得分:0)
如果不需要数学提取,您可以使用基于近似光线的算法进行算法,该算法具有相同的起始点,但是斜率已经被捕捉到集合中最接近的离散值。
例如,考虑将光线的角度四舍五入到最接近的程度。将有360个可能的角度值。如果我们考虑180个不同的线,每个线旋转一度,那么每条光线将平行于至少一条线。如果我们让我们的线组大小为l,我们将得到分辨率pi/(2l)
弧度近似光线的角度。
我们注意到,如果光线是轴对齐的,应该很容易找到光线的第一个交点。所以我们在不同的基础上进行计算。每条光线都会在其中一条基线上进行轴对齐,因此我们只需要找出找到一组x轴对齐光线的第一个交点的算法。
为此,我们为y轴构建segment tree,然后在分段树中进行查找以找到与x轴对齐光线相交的分段集。但是,由于我们不关心整个交叉段,但只关注沿着光线的第一个,我们不需要跟踪整个交叉点集合,因为我们走在树上,但只是我们拥有的最好的交叉点发现到目前为止。这将是运行时间log(n)+k
,其中k是交叉点的数量。但是k的因子可以并行化到log(n)。
因此每个基础需要一个分段树构造n*log(n)
,并且每个光线都需要在分段树中查找(log(n)+k)
因此总运行时间为
l*n*log(n)+n*(log(n)+k)
但它对于c来说都非常可并行化
(n/c)*(l*log(n)+k)
这是非常好的,但是k可能会随着n而缩放,这意味着即使有~n个核心,我们仍然有线性运行时间(并且由于k因子可以进一步并行化,因此下至log(n)具有多于n个核心)。线性运行时间优于线扫,但我仍然觉得我们可以做得更好,而我无法弄清楚如何。有趣的是,我们的算法可以找到与(n*l/c)*(log(n))
时间内从光线延伸的线相交的线段集,但是缓慢的部分是遍历每个交叉线段并检查它是否位于右侧原点,并检查它是否是第一个。即使在线扫描算法的并行版本中,您似乎也无法摆脱这个术语。
答案 2 :(得分:0)
您可以尝试line sweep algorithm的并行版本,只需将空间分开并在每个部分上并行执行多次线扫描。
如果将空间划分为s部分,并对每个部分进行线扫描,则可以获得一些加速。但是,它并不是令人尴尬的并行,因为您需要为每个中间起点执行多次初始化。
每次初始化都需要n*log(n)
种每个跨越该行位置的段,以及事件队列的n*log(n)
构造。然而,两者都令人尴尬地平行于n个核心。然后扫描提供了令人尴尬的并行加速,直到s。
因此,找到c核心交叉点的运行时间为
(s*n/c)*log(n) +((n+k)/s)*log(n)
其中k是总交叉点数。如果我们选择s = sqrt(c),那么我们得到
((n+k)/sqrt(c))*log(n)
换句话说,我们得到的速度与核心数的平方根成比例。
但是因为k是交叉点的总数,所以它可以像n ^ 2一样扩展,这会使事情变得更糟。即使c = n,我们的最坏情况也可能是
n^1.5 * log(n)
还有the optimal O(n log n + k) algorithm找到交叉点(其中k是交叉点的总数),它比线扫描算法快,但要复杂得多,所以我不知道它是如何工作的,或者如果它可以并行化。