Когда использовать NSEnumerationConcurrent - PullRequest
13 голосов
/ 24 июля 2011

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

Итак, у меня есть конкретный вопрос, и более общий.

Первый вопрос: Вот, возможно, немного надуманный пример использования блока для одновременной работы:

CGFloat GetAverageHeight(NSArray* people)
{
  NSUInteger count = [people count];
  CGFloat* heights = malloc(sizeof(CGFloat) * count);

  [people enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock:
  ^(id person, NSUInteger idx, BOOL* stop)
  {
    heights[idx] = [person height];
  }];

  CGFloat total= 0.0;
  for (size_t i = 0 ; i < count ; i++) total += heights[i];
  free(heights);
  return total / count;
}

Игнорируя тот факт, что непараллельное перечисление могло просто суммировать высоту напрямую, без необходимости вызова malloc или второй половины функции, есть ли здесь смысл использовать NSEnumerationConcurrent? Затраты на использование GCD (или все, что NSEnumerationConcurrent делает в фоновом режиме) сводят на нет выгоды от одновременного получения тривиального свойства? Насколько менее тривиальной должна быть работа блока, прежде чем она будет стоить использовать NSEnumerationConcurrent?

Второй вопрос: в более общем смысле, должен ли я считать параллелизм чем-то, что я должен использовать, когда вижу возможность сделать это (обоснование: предположительно, смысл этих API состоит в том, что они делают параллелизм менее частным случаем и большей частью общий состав программы) или просто оптимизацию, которую я должен использовать, только если я обнаружил конкретную проблему с производительностью и считаю, что параллелизм - это ответ (обоснование: ошибки в параллельном коде - это кошмар, чтобы выследить)?

1 Ответ

11 голосов
/ 25 июля 2011

Как правило, вы используете параллелизм только тогда, когда выполняемые операции являются относительно "тяжелыми". Даже в этом случае использование необработанного параллелизма, предлагаемого enumerateObjectsWithOptions:, может быть проблематичным, если параллелизм является неправильной гранулярностью для выполняемой задачи.

GCD действительно чертовски эффективен при постановке в очередь и обработке, но вполне вероятно, что этот код в конечном итоге вызовет malloc () для копирования блока (зависит от того, имеет ли блок уникальное захваченное состояние).

Ответ на ваш второй вопрос наполняет многие книги, самые бесполезные.

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

Одна ключевая точка; при рассмотрении параллелизма сосредоточьтесь на создании целых подграфов объектов с изолированной резьбой, за исключением чрезвычайно четко определенного граничного API, охватывающего потоки / очереди. Хороший пример этого - Core Data.

...