2009 英特尔® 线程挑战赛—线段求交
问题描述
问题:写一段多线程代码,用来查找在三维空间内相交的线段对。线段的输入文件和输出结果文件将在命令行上指定。
输入格式:输入文件是一个文本文件,它的第一行将包含一个整数 N,表示文件内包含的线段数。后面的 N 行将包含线段的 4 个(可打印)字符名称和用来表示线段两个 (x,y,z) 端点的 6 个整数。其中的前 3 个数字是一个端点的坐标,后 3 个数字是另一个端点的坐标。求解此问题的程序只要能在 32 位平台上正确运行即可满足精度要求。
输出格式:使用某种人类可读的格式。列出所输入线段中的每一对相交线段。如果没有交点,则打印一条消息说明无交点。
输入文件示例:
===================
8
AAAA -6 1 3 -8 4 -4
BBBB 3 -9 0 -3 -1 6
CCCC -1 -7 2 -4 -8 -4
DDDD 0 0 0 -1 6 2
EEEE 0 3 8 5 3 -7
FFFF 3 6 4 -4 0 -2
GGGG 8 -1 0 7 9 0
HHHH 11 8 0 10 -4 -3
输出文件示例:
====================
Segments that intersect:
DDDD FFFF
串行算法
在网络上查找了一些线段相交的资料,很多都是二维线段相交的算法,从《计算几何实验:Balaban线段求交算法(测试版)》一文中了解到,目前二维直线相交的最优算法是Balaban提出的分治算法,而比较简单实用的是Bentley & Ottmann的扫描线算法。
扫描线算法实现起来相对比较简单,只需要将所有直线按X坐标排序,然后依次对X有重合的线段进行跨立测试。这个算法可原封不动的推广到三维。
在判断做跨立测试前先根据两条直线决定的立方体是否有重叠,进行部分过滤。如果立方体没有重叠,那么这两条直线肯定不相交。然后选择XY平面做跨立测试,如果是跨立的,可进一步加入三维Z坐标做判断。
x0 + x1 * t1 = x2 + x3 * t2
y0 + y1 * t1 = y2 + y3 * t2
z0 + z1 * t1 = z2 + z3 * t2
三维直线若相交,那么上面三方程组成的方程组就一定有解,如果是线段相交就要求解的范围为0 <= t1,t2 <= 1。 由于前面已经在XY平面做了跨立测试,所以我们只需要判定第三个方程是否成立即可。
Balaban算法由是分治算法,它利用归一化的端点坐标值对线段进行排序,所以归一化的端点取值范围是[0, 2N](N为端点数量)。排序完成后将这个值分为多个条带,按线段与条带边界相交的情况分别进行处理,但其向三维推广较扫描线算法复杂很多。
最终选择了相对简单的扫描线算法进行实现。
并行算法
首先使用TBB的parallel_sort将线段按起点的X坐标排序。
然后,对排序后的每一条线段L仅需要判断起点在其右边且小于L的终点的线段判断是否相交。这样每条线段的判断都是相对独立的读访问线段参数及相交判断,不需要任何同步。所以OpenMP的for循环即可很容易的并行化本算法,但记录结果需要同步,使用TBB的concurrent_vector可以很方便快速的实现。
// 并行查找相交直线
#pragma omp parallel for schedule(guided, 1)
for(int i = 0; i < nLine; ++i)
{
const int nEndX = pLine[i].nX1;
// 根据X的排序结果只查找起点落在pLine[i].nX0到pLine[i].nX1之间的直线
for(int j = i + 1; j < nLine && pLine[j].nX0 <= nEndX; ++j)
{
……
}
}
优化工具
Hotspots检测
使用Intel Amplifier的Hotspots检测功能查找二分查找算法的热点函数,结果如下:
检测结果显示热点函数在XLSI_Solve内部,进一步深入发现大约8秒的时间用于定位是否可能相交,也就是绝大部分时间实际上都用于判断两条线段所决定的立方体是否重叠上,这个判断目前并不是最优的,只是使用了x坐标排序,yz坐标循环判断的方式,有更高效的方法可以大大缩短这个时间,例如使用分治法将直线划分到很多个立方体区域内,对每个立方体内部的线段及跨越多个立方体的线段分别处理。
Concurrency检测
使用Intel Amplifier的Concurrency检测功能查找可进行并行优化的代码,结果如下:
检测结果显示,加载数据部分是完全串行的,但由于占整个运行时间的比例较小,属于可以接受的范围,而进行线段相交判定的XLSI_Solve函数并行度较好,执行时间也比较长。
Locks and Waits检测
使用Intel Amplifier的Locks and Waits检测功能查找两种算法的锁和同步等待消耗,结果如下:
检测结果显示由于使用了concurrent_vector,导致有一定的同步开销可以通过优化提升性能。
其他优化
1. 在数据加载时保证线段两个端点X坐标的顺序X0 < X1以及计算部分数据减少运算。
2. 优化直线相交跨立测试部分,只是用乘法和加法运算。
性能测试
小数据量测试:
操作系统: 32bit的测试在32位XP下完成。
CPU: Intel(R) Core(TM)2 CPU 5270 @ 1.40GHz
内存: 1G
时间单位: 秒
直线数量 | 串行 | 并行 | 加速比 |
10000 | 0.770783 | 0.431424 | 1.79 |
20000 | 3.042050 | 1.676659 | 1.81 |
50000 | 19.585916 | 10.880589 | 1.80 |
编译说明
Windows平台:
使用VS2008和Intel Parallel Studio
1. 用VS2008打开本项目.
2. 选择X64平台Relase编译.
3. 进入Bin目录执行文件为XLSI.exe.
Linux平台:
使用ICC和TBB
1. 上传压缩包种的Src和Linux两个目录到服务器上.
2. 进入XLSI/Linux目录 执行make
3. 进入XLSI/Bin目录 执行文件为XLSI.
其他:
主办方请使用Win32平台Release版本测试,谢谢!
优化结论
本解决方案实现的三维直线求交的算法还有很大优化余地。特别是在缩小判定范围上做得不够,复杂度比较高。通过将Balaban算法的分治思想推广到三维的情况可大大降低复杂度,提高执行性能。
致谢
感谢Clay Breshears所做的解答,感谢Mu,Pryce为本文章发表到ISN所做的工作,感谢Xia, JeffX P为我的解决方案进行了认真细致的翻译。