Рекурсивная синхронизация быстрее, чем Рекурсивная асинхронность - PullRequest
2 голосов
/ 17 августа 2011

Почему Solution 2 более эффективен, чем Solution 1?

(время составляет в среднем 100 прогонов, и общее количество папок, через которые они проходят, составляет 13217)

// Solution 1 (2608,9ms)
let rec folderCollector path =
  async { let! dirs = Directory.AsyncGetDirectories path 
          do! [for z in dirs -> folderCollector z] 
              |> Async.Parallel |> Async.Ignore }

// Solution 2 (2510,9ms)
let rec folderCollector path =
  let dirs = Directory.GetDirectories path 
  for z in dirs do folderCollector z

Я бы подумал, что Solution 1 будет быстрее, потому что он асинхронный, и что я запускаю его параллельно.Что мне не хватает?

Ответы [ 4 ]

6 голосов
/ 17 августа 2011

Как уже ясно объяснили Даниэль и Брайан, ваше решение, вероятно, создает слишком много кратковременных асинхронных вычислений (поэтому накладные расходы больше, чем выгоды от параллелизма). Операция AsyncGetDirectories также, вероятно, не является неблокируемой, поскольку она не выполняет много работы. Я не вижу нигде по-настоящему асинхронной версии этой операции - как она определяется?

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

// Synchronous version
let rec folderCollectorSync path =
    let dirs = Directory.GetDirectories path 
    for z in dirs do folderCollectorSync z

// Asynchronous version that uses synchronous when 'nesting <= 0'
let rec folderCollector path nesting =
    async { if nesting <= 0 then return folderCollectorSync path 
            else let dirs = Directory.GetDirectories path 
                 do! [for z in dirs -> folderCollector z (nesting - 1) ] 
                     |> Async.Parallel |> Async.Ignore }

Вызов простой синхронной версии после определенного числа рекурсивных вызовов является распространенным приемом - он используется при распараллеливании любой очень древовидной структуры. Используя folderCollector path 2, это запустит только десятки параллельных задач (в отличие от тысяч), поэтому оно будет более эффективным.

В образце каталога, который я использовал (с 4800 подкаталогами и 27000 файлами), я получаю:

  • folderCollectorSync path занимает 1 секунду
  • folderCollector path 2 дубль занимает 600 мс (результат одинаков для всех вложений от 1 до 4)
3 голосов
/ 17 августа 2011

Из комментариев:

Ваша функция обойдется в async без каких-либо льгот, потому что

  1. вы создаете слишком много async с для выполнения короткого объема работы
  2. ваша функция не CPU, а скорее IO, связанная
1 голос
/ 17 августа 2011

Я ожидаю, что для такой проблемы у вас могут быть наилучшие результаты, если на верхнем уровне вы выполняете асинхронную / параллельную работу, но тогда вспомогательные вызовы должны быть синхронизированы. (Или если деревья очень глубокие, возможно, первые два уровня будут асинхронными, а затем синхронизируются).

Ключи балансировки нагрузки и детализации. Слишком маленькая часть работы, и издержки асинхронного вычисления перевешивают преимущества параллелизма. Таким образом, вам нужны достаточно большие куски работы для параллельного использования и преодоления накладных расходов. Но если заготовки слишком большие и несбалансированные (например, один каталог верхнего уровня имеет 10000 файлов, а 3 других каталога верхнего уровня имеют 1000 каждого), то вы также страдаете, потому что один парень занят, а остальные быстро заканчивают работу, и вы не максимизируйте параллелизм.

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

0 голосов
/ 20 августа 2011

Видимо, ваш код привязан к IO.Имейте в виду, как работают жесткие диски.Когда вы используете Async для многократного чтения, считывающие головки жесткого диска должны переключаться назад и вперед для одновременного обслуживания различных команд чтения, что приводит к задержке.Это, вероятно, станет намного хуже, если данные на диске сильно фрагментированы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...