Параллельные или распределенные вычисления — вещь сама по себе весьма нетривиальная. И среда разработки должна поддерживать, и DS специалист должен обладать навыками проведения параллельных вычислений, да и задача должна быть приведена к разделяемому на части виду, если таковой существует. Но при грамотном подходе можно весьма ускорить решение задачи однопоточным R, если у вас под руками есть хотя бы многоядерный процессор (а он есть сейчас почти у всех), с поправкой на теоретическую границу ускорения, определяемую законом Амдала. Однако, в ряде случаев даже его можно обойти.
Является продолжением предыдущих публикаций.
Типовой подход
Как правило, когда аналитик (DS специалист, разработчик или выберите себе любое подходящее название) пытается ускорить в рамках одного компьютера задачу и начинает двигаться от однопоточного режима к многопоточному, делает он это шаблонным образом. parApply, foreach\%dopar%, etc. Компактно и доходчиво можно поглядеть, например, здесь: «Parallelism in R». 3 шага:
- делаем core-1поток,
- запускаем с помощью foreach,
- собираем ответы и получаем результат.
Для типичных вычислительных задач, занимающих 100% CPU и не требующих передачи большого объема входной информации, это правильный подход. Основной момент, требующий внимания — обеспечить логирование в рамках потоков, чтобы иметь возможность контроля процесса. Без логирования полет пойдет даже без приборов.
В случае «enterprise» задач при их распараллеливании возникает множество дополнительных методологических сложностей, значительно снижающих эффект от приведенного выше прямолинейного подхода:
- возможная сильная разбалансировка нагрузки на потоки;
- требования к производительности CPU в рамках отдельной задачи могут иметь рваный характер с наличием только отдельных резких всплесков;
- каждое отдельное вычисления может требовать значительного объема памяти на вход и выдачу результатов также немалого объема;
- в рамках отдельной задачи может быть микс между вычислениями, работой с диском и запросом внешних систем.
Это совершенно типичный сценарий, когда в рамках процесса надо получить на вход объемное задание, прочитать данные с диска, забрать большой кусок из БД, поспрашивать внешние системы и дождаться от них ответа (классика — REST API запрос), а потом вернуть в родительский процесс N мегабайт в качестве результата.
Map-reduce по пользователям, локациям, документам,  ip- адресам, датам, … (дополните сами). В самых печальных случаях параллельное исполнение может оказаться более длительным, чем однопоточное. Также могут наблюдаться проблемы с нехваткой памяти. Все пропало? Вовсе нет.
Альтернативный путь
Рассмотрим тезисно способ радикального улучшения ситуации. При этом не забываем, что мы живем в рамках полного зоопарка. Продуктивный контур на *nix, DS ноутбуки на Win*nix\MacOS, а надо, чтобы везде работало единообразно.
- Микрозадача: получил на вход пользователя, запросил БД, запросил 2 внешних ИС через REST, скачал и разобрал справочник с диска, провел расчет, сбросил результат на диск\БД. Пользователей, например, 10^6.
- Переходим к использованию пакета futureи универсального адаптераdoFuture.
-  Если отдельные задачи таковы, что в рамках отдельных задач процессорное время нужно в малом объеме (ждем ответов сторонних систем), то doFutureпозволяет в одну строчку перейти от разбиения по потокам к разбиению по отдельным процессам (можно в*nixвhtopпоглядеть параметры запуска).
- Этих процессов можно создать гораздо больше чем ядер. Никакого клинча не произойдет поскольку отдельные процессы бывают в режиме ожидания большую часть времени. Но надо будет опытным путем подобрать оптимальное число процессов исходя из циклограммы типового процесса обработки.
Результат — исходная задача выполняется в разы быстрее. Ускорение может быть даже больше числа доступных ядер.
Кода нет сознательно, поскольку основная задача публикации — поделиться подходом и отличным семейством пакетов future.
P.S.
Есть еще несколько маленьких нюансов за которыми тоже надо проследить:
- каждый процесс будет потреблять память, включая получаемые и возвращаемые данные. Увеличение числа процессов кратно увеличит требования по доступной оперативной памяти.
- doFutureиспользует «магию» для автоматического определения состава передаваемых в процесс переменных и пакетов, но пускать все на самотек не стоит, лучше проверить.
- в процессах явное управление gcи явное удаление переменных с помощьюrmне помешает. Это не панацея и может не сработать, но явно указать удаляемые объекты будет в помощь.
- после завершения вычислений вызывайте plan(sequential). Это закроет все процессы и освободит занимаемую ими память.
- если необходимо передать в процесс большой объем данных рассмотрите возможность использования внешнего хранилища (диск, БД). Не забываем, что дескрипторы передать нельзя, открывать источник надо внутри самого процесса.
Предыдущая публикация — «Бизнес-процессы в enterprise компаниях: домыслы и реальность. Проливаем свет с помощью R».
 
           
 
AristarXXXX
Илья, спасибо за статью. Уже поставил в список на изучение. А мог бы ты подробнее расказать вот по этому поводу?
после завершения вычислений вызывайте plan(sequential). Это закроет все процессы и освободит занимаемую ими память.
i_shutov Автор
plan(multisession).plan(sequential)переводит процесс вычисления в однопоточный режим с соотв. завершением ранее созданных сессий.