Быстрое изменение элементов в списке

#c#

#c#

Вопрос:

Я попытался ускорить выполнение этого кода, используя Parallel.ForEach и ConcurrentBag, но он все еще работает слишком долго (особенно, если иметь в виду, что в моем сценарии я также могу быть 1.000.000 ):

 List<Point> points = new List<Point>();
for(int i = 0; i<100000;i  ) {
    Point point = new Point {X = i-50000, Y = i 50000, CanDelete = false};
    points.Add(point);
}

foreach (Point point in points) {
    foreach (Point innerPoint in points) {
        if (innerPoint.CanDelete == false amp;amp; (point.X - innerPoint.X) < 2) {
            innerPoint.Y = point.Y;
            point.CanDelete = true;
        }
    }
}
  

Комментарии:

1. Опишите, чего вы пытаетесь достичь. O (N ^ 2) — это слишком даже для N >= 20000 .

2. Если вы действительно собираетесь иметь в своей коллекции более миллиона элементов, возможно, вам захочется начать поиск алгоритмов получше, чем вложенный цикл… независимо от того, распределите ли вы это на несколько ядер с распараллеливанием или нет, все равно потребуется 10 ^ 12 итераций, что немало (и вы понимаете, что это приведет к удалению, например, линии точек, какой бы длинной она ни была, при условии, что точки находятся достаточно близко друг к другу, верно?)

3. Мне нужно получить все точки в точках с максимальным Y для каждого X , тогда как X может отличаться от x1-x2 <допуск. мои значения — это просто демонстрационные данные. реальные значения отличаются, но размеры списка похожи.

4. @Alexander: Я не вижу ничего max Y в вашей существующей логике.

5. @matti-virkkunen, как ты думаешь, есть ли точки. Где (p=> point.X-p.X<2) выполняется быстрее?

Ответ №1:

Этот код будет хуже выполняться параллельно из-за шаблонов доступа к данным.

Лучший способ ускорить это — признать, что вам не нужно рассматривать все O (N ^ 2) пары точек, а только те, которые имеют близкие координаты X.

Сначала отсортируйте список по координате X, O (N log N), затем выполняйте операции вперед и назад по списку из каждой точки, пока не покинете окрестности. Вам нужно будет использовать индексацию, а не foreach .

Если ваш образец данных, список уже отсортирован.

Поскольку ваш тест расстояния симметричен и удаляет совпадающие точки из рассмотрения, вы можете пропустить просмотр предыдущих точек.

 for (int j = 0; j < points.Length;   j) {
  int x1 = points[j].X;
  //for (int k = j; k >= 0 amp;amp; points[k].X > x1 - 2; --k ) { /* merge points */ }
  for (int k = j   1; k < points.Length amp;amp; points[k].X < x1   2;   k ) { /* merge points */ }
}
  

Улучшилась не только сложность, но и поведение кэша намного лучше. И это может быть разделено между несколькими потоками с гораздо меньшим конфликтом в кэше.

Комментарии:

1. Спасибо, Бен, это сработало! Очень быстро и корректно. Спасибо всем остальным, что также указали мне правильное направление.

Ответ №2:

Что ж, я не знаю точно, чего вы хотите, но давайте попробуем.

Во-первых, при создании списка вы можете задать его желаемый начальный размер, поскольку вы знаете, сколько элементов он будет содержать. Таким образом, ему не нужно постоянно расти.

 List<Point> points = new List<Point>(100000);
  

Затем вы могли бы отсортировать список по свойству X. Таким образом, вы бы сравнивали каждую точку только с ближайшими к ней точками: когда вы обнаружите, что первая, прямая или обратная, находится слишком далеко, вы можете прекратить сравнение.