查找点所属三角形的优化技巧

时间:2019-07-08 14:54:11

标签: c++ performance optimization parallel-processing parallelism-amdahl

我在优化算法时实际上遇到了一些麻烦:

我有一个圆盘(以0为中心,半径为1)填充了三角形(不一定具有相同的面积/长度)。可能会有大量的三角形(例如,从1k 300k 三角形)

我的目标是尽快找到一个点所属的三角形。 必须重复操作很多时间(大约 10k)。

目前,我使用的算法是:我正在计算每个三角形中该点的重心坐标。如果第一个系数在0到1之间,我继续。如果不是,我停下来。然后,我用相同的想法计算第二个系数,第三个计算,然后对每个三角形进行计算。

我想不出一种方法来利用我在光盘上工作的事实(以及我有一个欧氏距离来帮助我直接“瞄准”良好三角形的事实):如果我尝试计算从我的点到三角形的每个“中心”的距离:

1)它比使用重心坐标蛮力地进行操作要多得多

2)我将必须订购一个向量,该向量包含所有三角形到我的点的欧几里得距离。

3)我绝对不能保证最接近我的三角形的地方就是好三角形。

我觉得我缺少一些东西,可以预先计算,这可以帮助我在开始使用蛮力部分之前发现好“区域”。

该算法已经并行化(使用OpenMP):我正在并行调用以下函数。

bool Triangle2D::is_in_triangle(Vector2d Point) {
    double denominator = ((Tri2D(1, 1) - Tri2D(2, 1))*(Tri2D(0, 0) - Tri2D(2, 0)) + (Tri2D(2, 0) - Tri2D(1, 0))*(Tri2D(0, 1) - Tri2D(2, 1)));
    // Computing the first coefficient
    double a = ((Tri2D(1, 1) - Tri2D(2, 1))*(Point(0) - Tri2D(2, 0)) + (Tri2D(2, 0) - Tri2D(1, 0))*(Point(1) - Tri2D(2, 1))) / denominator;
    if (a < 0 || a>1) {
        return(false);
    }
    // Computing the second coefficient
    double b = ((Tri2D(2, 1) - Tri2D(0, 1))*(Point(0) - Tri2D(2, 0)) + (Tri2D(0, 0) - Tri2D(2, 0))*(Point(1) - Tri2D(2, 1))) / denominator;
    if (b < 0 || b>1) {
        return(false);
    }
    // Computing the third coefficient
    double c = 1 - a - b;
    if (c < 0 || c>1) {
        return(false);
    }
    return(true);
}

下一步可能是研究GPU并行化,但是我需要确保代码背后的想法足够好。

目前,2min30个三角形和75k个点大约需要 10k ,但这还不够快。


编辑:
Triangle2D使用本征矩阵存储坐标

4 个答案:

答案 0 :(得分:2)

所有留着长胡子的 HPC专业人士,确实允许在这里进行一些精心设计的方法,对于我的社区成员来说,这可能(根据我的诚实观点)很有趣(如果不是我所喜欢的话),感觉自己比专业人士感觉更初级,他们可能会对对性能驱动的代码设计,性能调整以及其他并行代码的风险和收益有更深的了解感兴趣,而这些正是您自己的核心HPC -计算经验是如此丰富和深刻。谢谢。

a)算法(按原样)可以使低挂果的速度提高约2倍,而且还有更多惊喜

b)其他算法可能会获得〜40〜80X的加速提速Duet2geometry

c)提供最佳并行代码和最佳性能的提示

目标 10k三角形 300k个点的目标运行时间为 2-在配备i5 8500、3GHz,6核,NVIDIA Quadro P400的计算机上进行3分钟(必须尝试GPU计算,甚至不确定是否值得)

enter image description here

尽管这似乎是一段漫长的旅程,但问题仍然存在,值得我们仔细观察。因此,请在表现为最高表现的思想过程中耐心等待。

a)算法(原样)分析:

按原样使用重心坐标系是一个不错的技巧,在最佳情况下,直接实现该方法的成本大约是(20 FLOP + 16 MEM / REG-I / O-ops),而略高于( 30个FLOP + 30个MEM / REG-I / O-ops。

有些修饰,可以避免发生一些昂贵甚至不重要的操作,从而可以降低执行成本:

--------------------------------------------------------------------------------------
double denominator = ( ( Tri2D( 1, 1 )
                       - Tri2D( 2, 1 ) // -------------------------- 2x MEM + OP-1.SUB
                         ) * ( Tri2D( 0, 0 ) //---------------------        + OP-3.MUL
                             - Tri2D( 2, 0 ) //--------------------- 2x MEM + OP-2.SUB
                               ) + ( Tri2D( 2, 0 ) //---------------        + OP-7.ADD
                                   - Tri2D( 1, 0 ) //--------------- 2x MEM + OP-4.SUB
                                     ) * ( Tri2D( 0, 1 ) //---------        + OP-6.MUL
                                         - Tri2D( 2, 1 ) //--------- 2x MEM + OP-5.SUB
                                           )
                       );
// Computing the first coefficient ------------------------------------------------------------------------------------------------------
double           a = ( ( Tri2D( 1, 1 )
                       - Tri2D( 2, 1 )  //-------------------------- 2x MEM + OP-8.SUB
                         ) * ( Point(0)   //------------------------        + OP-A.MUL
                             - Tri2D( 2, 0 ) //--------------------- 2x MEM + OP-9.SUB
                               ) + ( Tri2D( 2, 0 ) //---------------        + OP-E.ADD
                                   - Tri2D( 1, 0 ) //--------------- 2x MEM + OP-B.SUB
                                     ) * ( Point(1) //--------------        + OP-D.MUL
                                         - Tri2D( 2, 1 ) //--------- 2x MEM + OP-C.MUL
                                           )
                       ) / denominator; //-------------------------- 1x REG + OP-F.DIV //----------- MAY DEFER THE MOST EXPENSIVE DIVISION UNTIL a third coeff is indeed first needed, if ever------------[3]
if (a < 0 || a>1) { // ----------------------------------------------------------------------------- a < 0 ~~    ( sign( a ) * sign( denominator ) ) < 0
    return(false); // ------------------------------------------------------------------------------ a > 1 ~~  ||        a >         denominator
}
// Computing the second coefficient
double           b = ( ( Tri2D( 2, 1 ) - Tri2D( 0, 1 ) ) //--------- 2x MEM + OP-16.SUB
                     * (      Point(0) - Tri2D( 2, 0 ) ) //--------- 2x MEM + OP-17.SUB + OP-18.MUL
                     + ( Tri2D( 0, 0 ) - Tri2D( 2, 0 ) ) //--------- 2x MEM + OP-19.SUB + OP-22.ADD
                     * (      Point(1) - Tri2D( 2, 1 ) ) //--------- 2x MEM + OP-20.SUB + OP-21.MUL
                       ) / denominator; //-------------------------- 1x REG + OP-23.DIV //---------- MAY DEFER THE MOST EXPENSIVE DIVISION UNTIL a third coeff is indeed first needed, if ever -----------[3]
if (b < 0 || b>1) { // ----------------------------------------------------------------------------- b < 0 ~~   ( sign( b ) * sign( denominator ) ) < 0
    return(false); // ------------------------------------------------------------------------------ b > 1 ~~ ||        b >         denominator
}
// Computing the third coefficient
double c = 1 - a - b; // ------------------------------------------- 2x REG + OP-24.SUB + OP-25.SUB
//         1 -(a - b)/denominator; //--------------------------------------------------------------- MAY DEFER THE MOST EXPENSIVE DIVISION EXECUTED BUT HERE, IFF INDEED FIRST NEEDED <---HERE <----------[3]
  • 重复出现的重新评估,可以通过手动分配/重复使用来明确制定,但是,一个好的优化编译器有可能在使用的情况下被逐出。 -O3 强制执行优化标志。

  • do not hesitate to profile 甚至是挂得最低的水果,也可以用来抛光最昂贵的零件。

enter image description here


//------------------------------------------------------------------
double Tri2D_11_sub_21 = ( Tri2D( 1, 1 )
                         - Tri2D( 2, 1 )
                           ), //====================================================== 2x MEM + OP-a.SUB (REG re-used 2x)
       Tri2D_20_sub_10 = ( Tri2D( 2, 0 )
                         - Tri2D( 1, 0 )
                           ), //====================================================== 2x MEM + OP-b.SUB (REG re-used 2x)
       Tri2D_00_sub_20 = ( Tri2D( 0, 0 )
                         - Tri2D( 2, 0 )
                           ); //====================================================== 2x MEM + OP-c.SUB (REG re-used 1~2x)
//-----------------------
double denominator = ( ( /*
                         Tri2D( 1, 1 )
                       - Tri2D( 2, 1 ) // -------------------------- 2x MEM + OP-1.SUB (avoided by re-use) */
                         Tri2D_11_sub_21 //=========================================== 1x REG + OP-d.MUL
                         ) * ( /*
                               Tri2D( 0, 0 ) //---------------------        + OP-3.MUL
                             - Tri2D( 2, 0 ) //--------------------- 2x MEM + OP-2.SUB (avoided by re-use) */
                               Tri2D_00_sub_20 //===================================== 1x REG + OP-f.ADD
                               ) + ( /*
                                     Tri2D( 2, 0 ) //---------------        + OP-7.ADD
                                   - Tri2D( 1, 0 ) //--------------- 2x MEM + OP-4.SUB (avoided by re-use) */
                                     Tri2D_20_sub_10 //=============================== 1x REG + OP-e.MUL
                                     ) * ( Tri2D( 0, 1 ) //---------        + OP-6.MUL
                                         - Tri2D( 2, 1 ) //--------- 2x MEM + OP-5.SUB
                                           )
                       );
//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
//
// Computing the first coefficient ------------------------------------------------------------------------------------------------------
//
double enumer_of_a = ( ( /*
                         Tri2D( 1, 1 )
                       - Tri2D( 2, 1 )  //-------------------------- 2x MEM + OP-8.SUB (avoided by re-use) */
                         Tri2D_11_sub_21 //=========================================== 1x REG + OP-g.MUL
                         ) * ( Point(0)   //------------------------------------------        + OP-i.MUL
                             - Tri2D( 2, 0 ) //--------------------------------------- 2x MEM + OP-h.SUB
                               ) + ( /*
                                     Tri2D( 2, 0 ) //---------------        + OP-E.ADD
                                   - Tri2D( 1, 0 ) //--------------- 2x MEM + OP-B.SUB (avoided by re-use) */
                                     Tri2D_20_sub_10 //=============================== 1x REG + OP-l.ADD
                                     ) * ( Point(1) //--------------------------------        + OP-k.MUL
                                         - Tri2D( 2, 1 ) //--------------------------- 2x MEM + OP-j.MUL
                                           )
                       );/*denominator; *///------------------------ 1x REG + OP-F.DIV (avoided by DEFERRAL THE MOST EXPENSIVE DIVISION UNTIL a third coeff is indeed first needed, if ever-----------[3]

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~J.I.T./non-MISRA-C-RET-->
// TEST CONDITIONS FOR A CHEAPEST EVER J.I.T./non-MISRA-C-RET-->
//
if (  enumer_of_a > denominator                          // in a > 1, THE SIZE DECIDES, the a / denominator > 1, iff enumer_of_a > denominator         a rather expensive .FDIV is avoided at all
   || enumer_of_a * denominator < 0 ) return(false);     // in a < 0, THE SIGN DECIDES, not the VALUE matters, so will use a cheaper .FMUL, instead of a rather expensive .FDIV ~~ ( sign( a ) * sign( denominator ) ) < 0

//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
//
// Computing the second coefficient
//
double enumer_of_b = ( ( Tri2D( 2, 1 ) - Tri2D( 0, 1 ) ) //---------------------------------------- 2x MEM + OP-m.SUB
                     * (      Point(0) - Tri2D( 2, 0 ) ) //---------------------------------------- 2x MEM + OP-n.SUB + OP-o.MUL
                     + ( /*
                         Tri2D( 0, 0 ) - Tri2D( 2, 0 )   //--------- 2x MEM + OP-19.SUB + OP-22.ADD (avoided by re-use) */
                         Tri2D_00_sub_20 //======================================================== 1x REG + OP-p.ADD
                         )
                     * (      Point(1) - Tri2D( 2, 1 ) ) //---------------------------------------- 2x MEM + OP-r.SUB + OP-q.MUL
                       );/*denominator; *///------------------------ 1x REG + OP-23.DIV (avoided by DEFERRAL THE MOST EXPENSIVE DIVISION UNTIL a third coeff is indeed first needed, if ever-----------[3]

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~J.I.T./non-MISRA-C-RET-->
// TEST CONDITIONS FOR A 2nd CHEAPEST J.I.T./non-MISRA-C-RET-->
//
if (  enumer_of_b > denominator                          // in b > 1, THE SIZE DECIDES, the a / denominator > 1, iff enumer_of_a > denominator         a rather expensive .FDIV is avoided at all
   || enumer_of_b * denominator < 0 ) return(false);     // in b < 0, THE SIGN DECIDES, not the VALUE matters, so will use a cheaper .FMUL, instead of a rather expensive .FDIV ~~ ( sign( a ) * sign( denominator ) ) < 0

//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
//
// Computing the third coefficient
//
double c = 1 - ( ( enumer_of_a
                 - enumer_of_b
                   )
                 / denominator
                 ); // --------------------------------------------- 3x REG + OP-s.SUB + OP-t.FDIC + OP-u.SUB <----THE MOST EXPENSIVE .FDIV BUT HERE, IFF INDEED FIRST NEEDED <---HERE <------------[3]

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~J.I.T./non-MISRA-C-RET-->
// TEST CONDITIONS FOR PRE-FINAL RET J.I.T./non-MISRA-C-RET-->
//
if (   c < 0 || c > 1 ) return( false );

return( true ); //~~~~~~~~~~~~~~~~~~~ "the-last-resort" RET-->

b)<算法的其他方法:

让我们回顾一下另一种方法,它由于数据量少和指令少而看上去既更快又更便宜,并且一旦智能地使用矢量化AVX-2或更好的AVX-512矢量指令就有望具有高密度充分利用每个核心:ANIMATED, fully-INTERACTIVE explanation与分析问题的重新制定here一起。

点对线距离的三重测试(每条线为ax + by + c = 0)的便宜价格约为2 FMA3,足以进行符号测试(如果是矢量4合1紧凑型AVX-2 / 8合1 AVX-512 VFMADD -s甚至更好)

虽然可以“快速”决定是否要针对一个相应的三角形测试一个点,但可以将 polar(R,Theta) 中的每个三角形静态预“成帧”通过( R_min, R_max, Theta_min, Theta_max )的静态,预先计算的元组对空间进行坐标调整,并通过“快速”来区分每个点(如果它不适合这种极段)。但是,执行此操作的成本(数据访问模式成本+这些“快速”指令的成本)将超出不需要的任何可能的“已保存”指令路径(如果在极坐标段之外找到了一个点) )。在3.0+ GHz的频率下,每6个CPU内核以〜9个CPU指令的成本实现了24个三角点测试的性能范围,极地段的“预测试”将突然变得昂贵得令人无法接受。大约是二阶负面影响(通过更糟糕的缓存命中率/缓存未命中率引入,因为要存储更多数据并将其读入“快速”预测试〜每个三角形“成帧”极性为+ 16B,分段元组每点+ 8B(对高速缓存命中率/未命中率的影响最大)。

显然,这不是进一步行动的好方向,因为绩效将下降而不是提高,这是我们的全球战略。

Intel i5 8500 CPU只能使用AVX-2,因此如果需要,可以最紧凑地使用每个内核每个CPU时钟滴答8个三角形,甚至可以将性能提高2倍。

TRIPLE-"point-above-line"-TEST per POINT per TRIANGLE:
---------------------------------------------------------------------------------
PRE-COMPUTE STATIC per TRIANGLE CONSTANTS:
                     LINE_1: C0__L1, C1__L1, C2__L1, bool_L1DistanceMustBePOSITIVE
                     LINE_2: C0__L2, C1__L2, C2__L2, bool_L2DistanceMustBePOSITIVE
                     LINE_3: C0__L3, C1__L3, C2__L3, bool_L3DistanceMustBePOSITIVE

TEST per TRIANGLE per POINT (Px,Py) - best executed in an AVX-vectorised fashion
                     LINE_1_______________________________________________________________
                     C0__L1 ( == pre-calc'd CONST = c1 / sqrt( a1^2 + b1^2 ) ) //                       
                Px * C1__L1 ( == pre-calc'd CONST = a1 / sqrt( a1^2 + b1^2 ) ) // OP-1.FMA REG-Px,C1__L1,C0__L1
                Py * C2__L1 ( == pre-calc'd CONST = b1 / sqrt( a1^2 + b1^2 ) ) // OP-2.FMA REG-Py,C2__L1, +
           .GT./.LT. 0                                                         // OP-3.SIG
                     LINE_2_______________________________________________________________
                     C0__L2 ( == pre-calc'd CONST = c2 / sqrt( a2^2 + b2^2 ) ) //                       
                Px * C1__L2 ( == pre-calc'd CONST = a2 / sqrt( a2^2 + b2^2 ) ) // OP-4.FMA REG-Px,C1__L2,C0__L2
                Py * C2__L2 ( == pre-calc'd CONST = b2 / sqrt( a2^2 + b2^2 ) ) // OP-5.FMA REG-Py,C2__L2, +
           .GT./.LT. 0                                                         // OP-6.SIG
                     LINE_3_______________________________________________________________
                     C0__L3 ( == pre-calc'd CONST = c3 / sqrt( a3^2 + b3^2 ) ) //                       
                Px * C1__L3 ( == pre-calc'd CONST = a3 / sqrt( a3^2 + b3^2 ) ) // OP-7.FMA REG-Px,C1__L3,C0__L3
                Py * C2__L3 ( == pre-calc'd CONST = b3 / sqrt( a3^2 + b3^2 ) ) // OP-8.FMA REG-Py,C2__L3, +
           .GT./.LT. 0                                                         // OP-9.SIG

( using AVX-2 intrinsics or inlined assembler will deliver highest performance due to COMPACT 4-in-1 VFMADDs )
                                             ____________________________________________
                                            |  __________________________________________triangle A: C1__L1 
                                            | |  ________________________________________triangle B: C1__L1
                                            | | |  ______________________________________triangle C: C1__L1
                                            | | | |  ____________________________________triandle D: C1__L1
                                            | | | | |
                                            | | | | |      ______________________________
                                            | | | | |     |  ____________________________triangle A: Px
                                            | | | | |     | |  __________________________triangle B: Px
                                            | | | | |     | | |  ________________________triangle C: Px
                                            | | | | |     | | | |  ______________________triandle D: Px
                                            | | | | |     | | | | |
                                            |1|2|3|4|     | | | | |
                                            | | | | |     |1|2|3|4|      ________________
                                            | | | | |     | | | | |     |  ______________triangle A: C0__L1
                                            | | | | |     | | | | |     | |  ____________triangle B: C0__L1
                                            | | | | |     | | | | |     | | |  __________triangle C: C0__L1
                                            | | | | |     | | | | |     | | | |  ________triandle D: C0__L1
                                            | | | | |     | | | | |     | | | | |
                                            |1|2|3|4|     | | | | |     | | | | |
                                            | | | | |     |1|2|3|4|     | | | | |
                                            | | | | |     | | | | |     |1|2|3|4|
  (__m256d)    __builtin_ia32_vfmaddpd256 ( (__v4df )__A, (__v4df )__B, (__v4df )__C ) ~/ per CPU-core @ 3.0 GHz ( for actual uops durations check Agner or Intel CPU documentation )
  can
  perform 4-( point-in-triangle ) PARALLEL-test in just about ~ 9 ASSEMBLY INSTRUCTIONS / per CPU-core @ 3.0 GHz
         24-( point-in-triangle ) PARALLEL-test in just about ~ 9 ASSEMBLY INSTRUCTIONS / per CPU

  using AVX-512 empowered CPU, can use 8-in-1 VFMADDs
  could
  perform 8-( point-in-triangle ) PARALLEL-test in just about ~ 9 ASSEMBLY INSTRUCTIONS / per CPU-core @ 3.0 GHz
         48-( point-in-triangle ) PARALLEL-test in just about ~ 9 ASSEMBLY INSTRUCTIONS / per CPU 

c)最佳并行代码+最高性能的提示:

步骤-1: GPU / CUDA带来的收益与收益

如果您的博士导师,教授,老板或项目经理确实坚持要您针对此问题开发GPU计算的c ++ / CUDA代码解决方案,那么最好的下一步就是要求获取任何更合适的GPU卡以用于这样的任务,比您发布的任务还要重要。

您所指示的卡Q400 GPU has just 2 SMX (48KB L1-cache each)不太适合进行认真的并行CUDA计算,它几乎没有30X的处理设备可以实际执行任何SIMD线程块计算,更不用说它的小内存和小型L1 / L2 on SMX缓存。因此,在完成所有与CUDA相关的设计和优化工作之后,SIMD线程中将不会有更多(但只有一对)GPU-SMX warp32级的线程块执行,因此这款基于GP107的设备没有什么大的变化,并且有30多种X更好的设备可用于某些确实具有高性能的并行处理可用COTS)

第0步::预先计算和预先安排的数据( MAXIMISE缓存行的一致性):

在此处,优化算法的最佳宏范围循环是有意义的,这样您就可以从缓存命中中“受益”(即,最好重用已经预先提取的“快速”数据)

因此,可以测试一下,将工作分配给N个并行工作的人是否更快,而N个并行工作的人处理分离的三角形工作块,它们每个都循环到最小的内存区域上,所有点(〜10k * 2 * 4B〜80 kB),然后移动到工作块中的下一个三角形。确保行优先阵列对齐到内存区域是至关重要的(FORTRAN的人可以用HPC快速紧凑型/矢量化矢量代数和矩阵处理的技巧告诉很多费用/好处)

好处?

缓存的系数将重复使用约1万次(成本为 ~ 0.5~1.0 [ns] ,而不是重新获取成本+ 100 ~ 300 [ns] >如果必须通过RAM存储器访问来重新读取它们)。关于 ~ 200X ~ 600X的差异值得我们尽最大努力使从属于数据访问模式和缓存行资源的工作流保持最佳状态。

结果是原子的-任何点将属于一个且仅属于一个(不重叠)三角形。

这简化了对结果向量的先验非冲突且相对稀疏的写操作,在该向量中,任何检测到的三角形内点都可以自由报告找到的这种三角形的索引。

使用该结果向量来潜在地避免对已经进行过匹配(索引为非负数)的点进行任何重新测试的效率不是很高,因为重新读取此类指示并进行重新排列的成本4合1或8合1三角形点测试的紧凑对齐方式对于不重新测试已经映射的点的潜在“节省”将变得代价昂贵。

因此,10k指向一个 300k 三角形的块可能会产生以下工作流:
每个300k / 6个三角形的分割
~ 50k三角形/每个内核1个三角形
~ 50k * 10 k的三角形点测试
{{1 }} 每个内核的三角点测试~ 500M
@ 3.0+ GHz 每个内核的矢量4合1紧凑测试执行
~ 125M AVX-2 测试x ~ 125M条指令10 uops ...
@ 3.0 GHz ...秒)?是的,这里有很大的动力去实现这一最终性能,并以此方式指导进一步的工作。

所以,我们在这里

在6核AVX-2 i5 8500 @ 3.0+ GHz上,可以实现的300 + k三角形/ 10 + k点的可达到的HPC目标在几秒钟内

值得努力,不是吗?

答案 1 :(得分:1)

我认为您可以为每个三角形创建一个带有边界的数组:上,下,左,右极限。然后,将您的观点与这些边界进行比较。如果它在一个范围内,则查看它是否确实在三角形内。这样,99.9%的情况就不会涉及双浮点乘法和大量的加法运算-只是比较。仅当该点在三角形的直线极限内时,才进行计算量大的运算。

例如,这可以进一步加速。排序例如三角形的最顶端,并使用二进制搜索;然后首先找到三角形下方最高点,然后检查三角形上方的最高点。这样,您只需要检查一半以上即可。如果三角形的极值高度有上限,则可以检查得更少。请注意,后一种策略将使您的源代码更加复杂-因此,这将是确定要为优化多少结果而付出多少努力的情况。第一部分似乎很容易,并且很有帮助。排序列表:仅需将工作量减少近一半就可付出更多努力。我先看看第一个策略对您来说是否足够。

答案 2 :(得分:1)

如果必须执行大量单个查询,则四叉树是不错的选择,但是如果一次要完成10k,则有一个专门为此目的而构建的算法:sweep line。在单个线程中,对相似数据的查询时间不到5秒。

我们将截取一条垂直线,并从左到右在2d平面上扫一扫,然后跟踪该线在任何时间点相交的三角形。 因此,每当您的线在扫描时与其中一个查询点相交时,您只需检查垂直线重叠的三角形即可。没有其他三角形可以包含该点。

在这种情况下,我有点讨厌名称“ sweepline”,因为它给人的印象是平稳地横穿飞机,而实际上却没有。它只是按从左到右的顺序跳到下一个感兴趣的位置。

根据三角形与查询的比率,您可以还将重叠的三角形按其y坐标放置在segment treeinterval tree中,但这实际上要慢一些用于我的数据(或者我的实现不好。也可以)。不过绝对值得一试,特别是如果您可以推迟平衡树直到需要它的时候。

设置时间只有3种:

  • 获取指向三角形的指针的矢量,按三角形的最小x值排序
  • 获取指向三角形的指针的矢量,按三角形的最大x值排序
  • 获取指向按x值排序的指针的向量

然后我们进行清扫:

  • 将3个排序列表中的每个列表的“当前位置”初始化为0
  • 在3个排序列表中的当前位置寻找最低的x值。这将是扫掠线的下一个x位置
    • 如果它是三角形的最左点,则将该三角形添加到“重叠”向量中
    • 如果这是一个查询点,请对照所有当前重叠的tris来检查该点
    • 如果它是三角形的最右点,则将其从重叠矢量中删除。
  • 增加该列表的当前位置
  • 循环,直到您没有分数或三分。

为什么这比四叉树好?因为我们一直在跟踪当前与扫描线相交的Tris。这意味着我们在查询之间重用了数据,而四叉树并没有真正做到这一点。四叉树将具有更好的单查询性能,但是这种大容量查找是针对扫掠线进行的。

答案 3 :(得分:0)

使用boost的二次方rtree找到最接近的三角形可以很好地完成工作。算法现在不到一分钟就运行了(用于75k三角形和100k点(十倍!))

我最终通过在每个三角形中放入一个框,然后寻找一个点的Knn并测试相应的三角形来构建一棵树:) 本来期望进入像Spatial数据库这样的新域会遇到更多麻烦,但是Boost确实是一个疯狂的库