快速和改进的二维凸包算法及其在O(n log h)中的实现(实现部分)

来源:互联网 发布:日常工作安排软件 编辑:程序博客网 时间:2024/06/10 21:01

此篇接上一篇博客http://blog.csdn.net/firstchange/article/details/78588669

  • 实施选择

数组与列表

“List”类是一个C#集合,它使用一个数组作为其底层容器。使用“列表”而不是数组应该有类似的性能。测试证实,直接管理阵列的性能提升很小。这个差别太小,很难用一个数组来证明丢失的清晰度。这两个集合已被用于不同的实现,可以一起比较。

数组与树

所有的Ouellet(和Liu和Chen)实现都使用基于数组的容器,除了Ouellet AVL版本。Ouellet AVL和Ouellet AVL v2使用AVL树来存储潜在候选,而不是基于数组的容器。使用基于数组的容器意味着手动的二分法管理。而树本质上是在内部实行二分法。使用二分法得到适当的插入点确保了良好的性能,并且是留在O(n log h)中的主要关键。根据我的测试和生活中的一个实际用法,我怀疑在大多数情况下“h”应该保持在1000以下,这对使用数组或树木没有任何影响。但是,如果“h”可能非常大(在我的测试中超过50万分),那么拥有一棵树会更安全。除此以外,数组的优点是(与树结构相比):

数据连续在一起,实现更高的CPU缓存命中率。在大多数情况下,只有一次调用堆分配器才能获得足够大的数组以容纳所有的船体点。这是基于许多使用随机点生成器作为输入点源的测试,如在此处的基准测试中所述。实际执行保留1000分,以减少堆分配器请求。对于100万点,结果几乎总是保持在800点的赫尔点与所有正常的随机生成器(而不是“弧”之一)。可以使用索引,这使得直接访问点。实际上,直接访问可以在多线程算法中实现O(n)性能,但是一些测试显示比实际实现更慢的结果。性能下降似乎是由多线程锁定机制引起的。在O(n)中查看凸包,获取更多关于我做的快速测试的信息,这些信息没有像预期的那样工作。另外,因为在大多数情况下“h”非常低,所以应用在“n”上的多线程机制的添加应该是非常低的,以便不会比与“h”有关的时间更重要。O(n)测试不包含在提供的代码中。

在树结构上使用数组的主要缺点是:

当“h”的数量变大,大于〜500 000(Ouellet C#实现)时,阵列容器解决方案变得昂贵,原因有二:

主要原因是我们每次插入或删除候选人都要转移积分。另外,每次到达保留空间边界时,我们都必须分配一个新数组,并将所有数据复制到新数组。

树解(在使用AVL树的代码中)更新。我的意思是使用AVL树而不是数组是最新的主要实现更改。AVL树实现似乎是一个很好的选择,以获得直接依赖于其输出大小的算法,而不是更高。这将确保在所有情况下都能保持更稳定的表现。但在一般用例中不需要,因为在所有测试的情况下,“h”(船体点)比“n”(源点)小很多。C ++实现不使用树的原因是我最近才意识到数组的负面影响,当“h”变得太大,我错过了用C ++编写另一个实现的时间。

有2种最流行的平衡树的树管理算法:AVL和“红黑”树。我选择AVL在“红黑”树上有两个原因:

懒惰,在我看来,在红黑树上实现AVL更容易。根据我的测试,因为“h”对于“n”来说保持非常小,所以应该比在树上插入更多的读取,这应该偏爱AVL树而不是红黑树以获得最佳性能。
  • O(n)中的凸包

我试图在O(n)而不是O(n log h)中创建一个算法。我想我可以借助线程,数组作为容器和良好的设计。至少需要2个线程才能达到目标。一个线程,快速筛选潜在的候选人,另一个插入潜在的候选人在结果凸包容器,一个数组。

线程1 - 每个点在O(1)中筛选候选。那个线程拒绝不好的候选人,把好的一个人放进一个堆栈。为了将近似的插入位置放入已经找到的候选凸包点的数组中,算法应该在第一点和最后点之间使用线性定位。这不应该给出插入的确切位置(弧线性化),但应该足以快速拒绝大多数点。这样,就没有更多的二分法,这就相当于去掉了“log h”。该线程在O(n)中完成它的工作。线程2-尝试插入由线程1过滤的潜在凸包点。这在O(〜h ++ log h)中完成。当接近n点和/或接近溶液时,插入应该随着时间的推移而越来越少。这应该有足够的时间来清空堆栈的点来尝试插入。

如果线程1总是在线程2之前完成,或者非常接近(恒定时间),那么我们可以说在O(n)中有一个凸包。

2个线程之间的首选容器是堆栈,因为最近评估的点具有更高的风险,成为更好的凸包候选。

事实上,这是工作,但工作的时间是Ouellet单线程版本的两倍。请注意,在大多数情况下,线程1在可能的候选堆栈中没有点数完成,这应该表示O(n)成功,至少在一般情况下是成功的。

此外,该算法之间交替2个不变数组副本,以保持连贯性,而不是减缓筛选候选人的线程。由于几个估计的原因,这不是一个真正的成功:

在一般情况下,它比其他任何我的实现慢两倍,可能是由于访问共享的过滤候选堆栈所需的锁定机制。虽然我使用的是“SpinLock”,而不是标准的“Lock”,这应该会更快,但是我觉得阻塞的情况太多了,导致了延迟。我应该找到一个没有阻碍的方法来插入潜在的候选人。它高度依赖于一个很好的随机分布点。这可能是非常糟糕的,否则因为过滤可能有非常糟糕的丢弃命中,至少我实现我的算法的方式。如果有可能的话,我将不得不重做一切,以使其更有效率,更少依赖数据。我确定了每象限的线程数量,而不是帮助其他象限完成他们的初始象限/作业完成任务。看到结果后,我决定停止梦想O(n)真实世界的用法。由于常规算法中每一步的简单性和速度,添加一个锁定机制会使事情减慢太多。也许在很多年的时候,千万分的时候会少点,而线程的数量会越来越高,那么这也许会被认为是有用的。我认为总之,缓慢问题来自一般在1000以下的小“h”,这是8-10的树。做10次迭代是如此之快以至于它可以很好地与任何锁定机制竞争。

作为一个方面说明:由于我缺乏知识,我不确定我是否有权说如果一个算法需要很多线程来实现这个性能,那么这个算法是在“O(n)”中的呢?我也必须去寻找那个 所有关于在O(n)中找到赫尔点的东西都必须被证明,而且真的很有趣,但时间和金钱统治世界,而我也是其中的一部分。由于性能不佳,因为我认为我应该改进算法,我宁愿不发表该部分。

多线程

根据基准测试结果,很容易看出多线程版本是单线程版本的两倍以上。由于Ouellet Hull算法的性质,它确实使它成为一个很容易实现多线程的候选者,至少在3的2个第一步中。2个第一步依赖于“n”而第三个是仅依赖于“h”。3个步骤的,第一很容易被完全多线程的,所述第二可容易地在4个完全独立的线程执行(不需要同步机构)和3 次的步骤将是有点难以多线程但它不受由“n “只能通过”h“。

尽管多线程的使用并不像算法的复杂性(大O)那么重要,但它可以帮助提高性能。在Ouellet算法的情况下,添加多线程并保持线程完全独​​立是非常容易的,这是额外的好处。

  • 算法基准

随机生成器,基准测试有五种不同类型的点生成器:

硬件用于测试

  • “速度测试”的结果

所提出的大部分结果都是针对O(n log h)中的算法来更好地区分最快者的表现。大多数测试都是用2台发电机完成的,“圆”和“扔掉”应该更接近实际使用。任何其他的算法组合,随机生成器都可以使用提供的代码轻松测试。如果您愿意,也可以添加自己的生成器或算法。

C / C ++中的实现结果是以本地语言编写的,而不是C#代码,以防止结果转换时间。这应该更好地了解每个算法/实现所花费的实时性。

直接基于结果点的线性回归也被添加以便查看趋势以及是否是线性的。

  • 关于“速度测试”结果的结论

    O(n log h)的优点是显而易见的。单调链算法是评估算法实现中速度最慢的算法尽管MiConvexHull和Heap算法都在O(n log n)中,但Heap非常慢。速度取决于源点,它们的位置和顺序。速度结果可能因点排列(随机发生器)而异。O(n log h)中的所有凸包算法都比较快,但它们之间存在很好的差异。Ouellet对刘和陈的优化带来了更好的表现。基于数组的容器实现依赖于非常大的“h”。当“h”达到约50万分时出现问题。Chan比Ouellet更独立于随机生成器的类型。比较不同随机生成器的结果时可以看出这种差异。Ouellet AVL和Ouellet AVL v2比Chan更受“h”的影响。通过比较“电弧发生器”和其他发电机的结果可以看出。这应该是由堆分配器延迟和/或重新平衡树造成的。多线程是一个真正的优势。C ++比C#快很多。虽然ne是完全相同的算法,但差异比预期的要高很多。我认为这可以归因于大量的内存读/写,这可能在C ++中具有较少的间接性,也可能是由于在C#中为了安全而自动发生的数组边界检查,而不是在C ++中。C#单线程vs Chan根据生成器的类型而不同。对于“圆”点生成器,C#比C中的Chan要快。使用“Throw away”生成器时,Chan很容易获胜。Ouellet CPP比Chan的速度快4倍,两者都是用相同的语言编写的(C / CPP)。那是高于100000的点数。不可变数组拷贝受到许多点的影响比使用重叠拷贝时的速度要快很多(正常使用,比如列表)。尽管Ouellet CPP在正常使用情况下明显胜出,但是由于基于阵列的用于存储船体点的“h”变得太大,所以它表现不佳。当点数变大时,Ouellet AVL和Chan之间的区别正在减小。查看“深度性能测试”的结果。对于Ouellet来说,AVL树似乎是确保在所有情况下都有良好性能的最佳容器。
  • “深度性能测试”的结果

这个测试主要显示算法速度的比较。结果分为两部分:

第一张表显示原始速度结果第二个表显示相同的信息,相对于同一行上最快的算法的比率

“深度性能测试”如何实现

对于每个点数(列:点数)    每次测试10次        创建一个随机点数(ptsCount)        执行每个算法计算其所花费的时间    对于每个算法,根据10次测试做一个平均值(每个算法都有一个更好的标准化平均值)

这种类型的测试的一个优点是它是在一定数量的点上完成的,但是对于每一个数量,平均值是由10个不同的随机点组成的,每个算法对相同的一组点进行测试。在10次测试中计算平均值有助于获得更可靠的结果,更好地反映实际情况。它降低了算法计算性能的变化。

还剩下什么

还剩下很多东西:

用红黑树代替Avl树测试Ouellet Avl并比较性能在C ++中实现AVL版本以查找语言优势,并查看与Ouellet ST的差异是否相同 在所有版本中添加一些或全部  优化在C ++中执行多线程版本,就像在C#中完成的一样,使用AVL树改进多线程版本,以便在每次传递中使用所有情况下的所有线程。找到更好的实现选择,并对O(n)中运行的算法进行更多测试通过使其更通用,使算法适应3D和/或任何维度。这应该是可行的。作为一个想法,我们可以使用位值作为象限。结合前面的任何一个来获得最好的算法实现
阅读全文
0 0
原创粉丝点击