Советы по оптимизации, чтобы определить, к какому треугольнику относится точка - PullRequest
2 голосов
/ 08 июля 2019

У меня действительно есть некоторые проблемы с оптимизацией моего алгоритма:

У меня есть диск (с центром в 0, с радиусом 1), заполненный треугольниками (не обязательно одинаковой площади / длины).Может быть ОГРОМНОЕ количество треугольников (скажем, от 1k до 300k треугольников)

Моя цель - найти как можно быстрее, какому треугольнику принадлежит точка.Операция должна повторяться большое количество времени (около 10k раз ).

На данный момент алгоритм, который я использую:вычисление барицентрических координат точки в каждом треугольнике.Если первый коэффициент находится между 0 и 1, я продолжаю.Если это не так, я останавливаюсь.Затем я вычисляю второй коэффициент с той же идеей и третий, и делаю это для каждого треугольника.

Я не могу придумать, как использовать тот факт, что я работаю на диске (и тот факт, что у меня есть евклидово расстояние, чтобы помочь мне «нацелиться» на хорошие треугольники напрямую): если я пытаюсь вычислить расстояние от моей точки до каждого «центра» треугольников:

1) это уже больше операций, чем то, что я делаю, когда я грубо форсирую его с барицентрическими координатами

2) Мне придется заказать вектор, содержащий евклидовы расстояния всех треугольников, чтобымоя точка.

3) У меня нет абсолютно никаких гарантий, что ближайший к моей точке треугольник будет хорошим треугольником.

Я чувствую, что что-то упустил,и что я мог бы предварительно вычислить, что-то, чтобы помочь мне определить хорошую «область» перед запуском части грубой силы.

Алгоритм уже распараллелен (используя OpenMP): я вызываю функцию ниже дляparallel for.

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);
}

Следующим шагом, вероятно, является рассмотрение параллелизации графического процессора, но мне нужно убедиться, что идея кода достаточно хороша.

На данный момент это занимает примерно 2min30 для 75k треугольников и 10k точек, но этого недостаточно.


Редактировать:
Triangle2Dиспользует собственную матрицу для хранения координат

Ответы [ 3 ]

2 голосов
/ 09 июля 2019

Все длиннобородые HPC-профессионалы , пожалуйста, разрешите немного научный подход, который может (по моему честному мнению) стать интересным, если не понравится нашим членам Сообщества, которые чувствуют сами они немного младше, чем вы профессионально себя чувствуете, и которые могут заинтересоваться более глубоким взглядом на проектирование кода, мотивируемое производительностью, настройкой производительности и другими рисками и преимуществами параллельного кода, которые вы знаете на своем собственном жестком ядре HPC- Компьютерный опыт так хорошо и так глубоко. Спасибо.

а) АЛГОРИТМ (как есть) может получить ~ 2-кратное ускорение для низко висящих фруктов + еще больше сюрпризов2в

б) ДРУГОЙ АЛГОРИТМ может получить ~ 40 ~ 80-кратное ускорение из-за геометрии

в) СОВЕТЫ ПО ЛУЧШЕМУ ПАРАЛЛЕЛЬНОМУ КОДУ + ОТЛИЧНАЯ ЭФФЕКТИВНОСТЬ

ЦЕЛЬ : целевое время выполнения для 10k точек в 300k треугольников будет 2-3 мин на компьютере с i5 8500, 3GHz, 6core, NVIDIA Quadro P400 (нужно попробовать вычисления на GPU, даже не уверен, стоит ли это того)

enter image description here

Хотя это может показаться долгим путешествием, проблема приятна и заслуживает более пристального внимания, поэтому, пожалуйста, потерпите меня во время потока мотивированных мыслей с максимальной эффективностью.

а) АЛГОРИТМ (как есть) АНАЛИЗ:

Барицентрическая система координат «как есть» является хорошим трюком, прямая реализация которого стоит чуть больше, чем примерно (20 FLOPs + 16 MEM / REG-I / O-ops) в лучшем случае и чуть выше ( 30 FLOPs + 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 флаг принудительной оптимизации.

  • не стесняйтесь профилировать даже этот низко висящий фрукт для полировки самых дорогих деталей.

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-->

б) ДРУГОЙ ПОДХОД К АЛГОРИТМУ:

Давайте рассмотрим другой подход, который кажется и более быстрым, и более дешевым, из-за меньшего количества данных и наименее инструкций, а также обещает иметь высокую плотность, когда разумное использование векторизованных векторных инструкций AVX-2 или более эффективных AVX-512 приведет к получить упряжку для каждого ядра: АНИМИРОВАННОЕ, полностью ИНТЕРАКТИВНОЕ объяснение в совокупности с переформулировкой аналитической проблемы здесь .

Тройной тест с расстоянием точка-линия (каждая строка 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 ) и «быстрый» различают каждую точку, если она не помещается внутри такого полярного сегмента.Тем не менее, затраты на это (затраты на доступ к данным + стоимость этих «быстрых» инструкций) превысят любые потенциально «сохраненные» пути инструкций, которые не должны иметь место (если точка находится за пределами полярного сегмента).).Достигнув диапазона производительности в 24 теста «точки в треугольнике» при стоимости ~ 9 инструкций ЦП на 6 ядер ЦП при 3,0+ ГГц, «предварительное тестирование» в полярном сегменте внезапно станет чрезмерно дорогим, если не говоритьо негативном эффекте второго порядка (вызванном гораздо худшим соотношением попаданий в кэш / пропаданий в кэше, учитывая, что больше данных нужно хранить и считывать в «быстрый» предварительный тест ~ + 16B на треугольник, «обрамляющий» полярныйсегмент сегмента + 8B на точку (с наихудшим влиянием на соотношение попаданий и промахов в кэше).

Это явно не хорошее направление для дальнейшего движения, так как производительность будет снижаться, а не увеличиваться, что является нашейглобальная стратегия здесь.

Процессор Intel i5 8500 можно использовать, но не AVX-2, поэтому наиболее компактное использование 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 стоит v / sПреимущества

Если ваш PhD-наставник, профессор, начальник или руководитель проекта действительно настаивает на том, чтобы вы разработали решение для кода на языке C ++ / CUDA на GPU-вычислениях для этой самой проблемы, лучшим следующим шагом будет поиск лучшего решения.GPU-карта для такой задачи, чем та, которую вы выложили.

На указанной вами карте, Q400 GPU есть только 2 SMX (48 КБ L1-кеш каждый) не совсем подходит для серьезно подразумеваемых параллельных CUDA-вычислений, имея около 30-кратных вычислительных устройств для выполнения любых SIMD-потоковых вычислений, не говоря уже о его малой памяти и крошечных L1 / L2-кешах на SMX.Таким образом, после всех усилий по проектированию и оптимизации, связанных с CUDA, в SIMD-потоках будет не больше, а одна (!) Пара потоковых блоков GPU-SMX warp32 в масштабе всего потока, поэтому большихЦирк следует ожидать от этого устройства на базе GP107, и для некоторых действительно высокопроизводительных параллельных обработок COTS имеется более 30 устройств с улучшенной X-скоростью

Шаг 0: предварительное вычисление иПредварительно упорядочить данные ( MAXIMIZE строка кэша COHERENCY ):

Здесь имеет смысл оптимизировать наилучший макроскопический цикл алгоритма, чтобы вы "извлекали выгоду" больше всего из попаданий в кэш (т. е. лучшее повторное использование «быстрых» данных, уже предварительно извлеченных)

Итак, может проверить, является ли кэш-память быстрее распределять работу среди N-одновременных рабочих, которые обрабатывают разрозненную работу-блоки треугольников, где каждый из них проходит по наименьшей области памяти --- все точки (~ 10k * 2 * 4B ~ 80 кБ), прежде чем перейти к следующему треугольнику в рабочем блоке.Очень важно обеспечить выравнивание массива по строке в области памяти (ребята из FORTRAN могут многое рассказать о затратах и ​​преимуществах только этого трюка для высокопроизводительной компактной / векторизованной векторной алгебры и матричной обработки HPC)

Преимущество?

Кэшированные коэффициенты будут повторно использоваться ~ 10 000 раз (по стоимости ~ 0.5~1.0 [ns] вместо затрат на повторное извлечение + 100 ~ 300 [ns], если они должны быть перечитаны при доступе к памяти RAM).Разница в том, что около ~ 200X ~ 600X стоит усилий, чтобы лучше выровнять рабочий процесс, подчиненный шаблонам доступа к данным и ресурсам строки кэша.

Результаты являются атомарными - любая точка будет принадлежатьк одному и только одному (непересекающемуся) треугольнику.

Это упрощает априорные неколлизионные и относительно редкие записи в результирующий вектор, в который любая обнаруженная точка внутри треугольника может свободно сообщать индекс найденного такого треугольника.

Использование этого вектора результатовпотенциально избегать повторного тестирования в точках, где уже было выполнено совпадение (индекс не отрицательный), не очень эффективно, поскольку затраты на повторное считывание такой индикации и реорганизацию компактного выравнивания из 4 вТесты «точка-в-треугольнике» 1 или 8 в 1 могут стать слишком дорогими для потенциальной «экономии», если не проводить повторное тестирование уже нанесенной на карту точки.

Таким образом, 10k указывает на блок 300k треугольники могут дать рабочий процесс:
разделение некоторых 300k / 6 ядер
~ 50k треугольники / 1 ядро ​​
~ 50k * 10 k тестирование точек на треугольник на ядро
~ 500M тесты «точка-треугольник» на ядро ​​@ 3.0+ GHz
~ 125M AVX-2 компактные тесты «вектор-4-в-1» на ядро
~ 125M тесты x 10 uops -инструкции @ 3.0 GHz ...
что такое 1.25G uops @ 3.0+ GHz ... секунд?Да, здесь есть довольно сильная мотивация, чтобы идти к этой конечной производительности и направлять дальнейшую работу таким образом.

ТАК, НАМ МЫ:

Принципиально достижимая цель HPC - этов диапазоне нескольких секунд для 300 + k треугольников / 10 + k очков на 6-ядерном AVX-2 i5 8500 @ 3.0+ ГГц

Стоит усилий, не так ли?

1 голос
/ 08 июля 2019

Я думаю, вы могли бы сделать массив с границами для каждого треугольника: верхняя, нижняя, правая и левая крайности. Затем сравните свою точку зрения с этими границами. Если оно попадает в один, то посмотри, действительно ли оно в треугольнике. Таким образом, в случае с 99,9% не требуется двойного умножения с плавающей точкой и ряда дополнений - только сравнения. Вычислительно дорогостоящие операции выполняются только в том случае, если точка находится в пределах прямолинейных крайностей треугольника.

Это может быть еще ускорено, например, сортировка, например, крайний край треугольника, использующий двоичный поиск; а затем начните с нахождения точки, которая является самой высокой точкой ниже вашего треугольника, а затем проверяйте точки над ним. Таким образом, вам нужно будет проверить чуть больше половины. Если бы была верхняя граница высоты крайностей треугольников, вы могли бы проверить гораздо меньше. Обратите внимание, что эта последняя стратегия сделает ваш исходный код более сложным, так что это будет случай, когда вы определите, сколько усилий вы хотите приложить к оптимизации для достижения какого-то результата. Кажется, что первая часть будет довольно простой и очень поможет. Сортированный список: больше усилий для почти вдвое сокращения ваших операций. Я бы посмотрел, хватит ли вам первой стратегии в первую очередь.

0 голосов
/ 09 июля 2019

Использование квадратичного rtree буста для нахождения ближайшего треугольника отлично сработало.Алгоритм работает менее чем за минуту (для 75k треугольников и 100k точек (в 10 раз больше!))

В итоге я построил дерево, вставив в каждый треугольник коробку, и искал Knn ofопределите точку и протестируйте соответствующие треугольники :) Ожидалось, что в новый домен, такой как Пространственная база данных, будет больше проблем, но Boost действительно безумная библиотека

...