Почему я не должен использовать асинхронные рабочие процессы F # для parallelism?

Недавно я изучал F #, особенно интересуясь простотой использования данных parallelism. Идиома data |> Array.map |> Async.Parallel |> Async.RunSynchronously кажется очень простой для понимания и простой в использовании и получения реальной ценности.

Так почему же async на самом деле не предназначен для этого? Сам Дональд Симе говорит, что PLINQ и Futures, вероятно, лучший выбор. И другие ответы, которые я прочитал здесь, согласны с этим, а также рекомендуют TPL. (PLINQ не слишком сильно отличается от вышеупомянутых встроенных функций, если вы используете F # Powerpack для получения функций PSeq.)

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

Итак, почему я не должен использовать async для выполнения параллельных процессов данных? Что я проиграю, написав параллельный код async вместо использования PLINQ или TPL?

Ответ 1

Я написал статью, которая повторяет один образец С# TPL с использованием как Task, так и Async, который также имеет некоторые комментарии к различию между ними. Вы можете найти его здесь, а также есть более расширенная версия на основе async.

Вот цитата из первой статьи, в которой сравниваются два варианта:

Выбор между двумя возможными реализациями зависит от многих факторов. Асинхронные рабочие процессы были разработаны специально для F #, поэтому они более естественно соответствуют языку. Они обеспечивают лучшую производительность для задач, связанных с вводом-выводом, и обеспечивают более удобную обработку исключений. Более того, последовательный синтаксис довольно удобен. С другой стороны, задачи оптимизированы для вычислений с привязкой к ЦП и упрощают доступ к результатам вычисления из других мест приложения без явного кэширования.

Ответ 2

Итак, почему я не должен использовать async для выполнения параллельных процессов данных?

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

Обратите внимание, что ваш пример можно написать еще более элегантно, используя TPL из F #:

Array.Parallel.map f xs

Что я проиграю, написав параллельный асинхронный код вместо использования PLINQ или TPL?

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

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

Ответ 3

Я всегда полагал, что TPL, PLinq и т.д. дают вам сверх того, что делает Async. (Механизм отмены - это тот, который приходит на ум.) Этот вопрос содержит несколько лучших ответов.

В этой статье намечено небольшое преимущество в производительности для TPL, но, вероятно, этого недостаточно, чтобы быть значительным.