Выбор минимума среди минимумов с помощью Parallel.ForEach - PullRequest
6 голосов
/ 24 июля 2010

Я новичок в C #, Parallel.ForEach и .NET в целом. Я хочу распараллелить поиск, который включает тысячи местоположений. Для каждого местоположения я вычисляю большое расстояние по кругу. Это расчет, который я хочу распространить на разные ядра. Мой вопрос: как мне это сделать, если у меня есть только одна локальная переменная потока, как в этом примере MSDN TPL ? В результате я посмотрел на Interlocked и увидел его параметры Add, CompareExchange, Decrement, Exchange, Increment и Read, но я не просто добавляю, увеличиваю, уменьшаю или проверка на равенство. Я хочу вернуть объект через несколько параллельных потоков, который имеет самое короткое общее расстояние. Моя интуиция говорит, что это должно быть легко, что я должен быть в состоянии создать какой-то маленький объект, охватывающий Location и расстояние, но как мне получить лучший ответ из каждого потока и , затем выбрать самый короткий расстояние между ними? Вот непараллельная версия:

Location findClosestLocation(Location myLocation, List<Location> allLocations)
{
  double closest = double.MaxValue;
  Location closestLoc = null;
  foreach (Location aLoc in allLocations)
  {
    if (aLoc != myLocation)
    {
      double d = greatCircle(myLocation, aLoc);
      if (d < closest)
      {
        closest = d;
        closestLoc = aLoc;
      }
    }
  }
  return closestLoc;
}

Я действительно увидел пост в блоге DDJ , который, казалось, предлагал хороший совет, но мне было интересно, был ли это лучший совет. Я вижу, как автор перебирает массивы, и задаюсь вопросом, нет ли более функционального способа сделать это. В функциональном мире я бы использовал map, lambda и min.

1 Ответ

11 голосов
/ 24 июля 2010

Самый простой вариант - переключиться на PLINQ:

Location findClosestLocation(Location myLocation, List<Location> allLocations)
{
     return allLocations
               .AsParallel()
               .Min(location => greatCircle(myLocation, location));
}

При этом это просто агрегирование с параллельными конструкциями . У вас есть несколько вариантов, если вы хотите придерживаться класса Parallel. Одним из вариантов было бы синхронизировать это внутри блока, используя блокировку. Я не рекомендовал бы это, поскольку это повредит вашей общей производительности.

Лучшим вариантом является использование Parallel.ForEach методов, обеспечивающих локальное состояние. Они позволят вам переписать это как:

Location findClosestLocation(Location myLocation, List<Location> allLocations)
{
  double closest = double.MaxValue;
  Location closestLoc = null;
  object sync = new object();

  Parallel.ForEach<Location, Tuple<double,Location>(
      allLocations,
      () => new Tuple(double.MaxValue, null),
      (location, loopState, localState) =>
      {
          double d = greatCircle(myLocation, aLoc);
          if (d < localState.Item1)
              return new Tuple(d, aLoc);
          else
              return localState;
      },
      localState =>
      {
          lock(sync)
          {
              if (localState.Item1 < closest)
              {
                  closest = localState.Item1;
                  closestLoc = localState.Item2;
              }
          }
      }
  );
  return closestLoc;
}

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

...