Хабрахабр

Несколько соображений по поводу параллельных вычислений в R применительно к «enterprise» задачам

И среда разработки должна поддерживать, и DS специалист должен обладать навыками проведения параллельных вычислений, да и задача должна быть приведена к разделяемому на части виду, если таковой существует. Параллельные или распределенные вычисления — вещь сама по себе весьма нетривиальная. Однако, в ряде случаев даже его можно обойти. Но при грамотном подходе можно весьма ускорить решение задачи однопоточным R, если у вас под руками есть хотя бы многоядерный процессор (а он есть сейчас почти у всех), с поправкой на теоретическую границу ускорения, определяемую законом Амдала.

Является продолжением предыдущих публикаций.

parApply, doreach\%dopar%, etc. Как правило, когда аналитик (DS специалист, разработчик или выберите себе любое подходящее название) пытается ускорить в рамках одного компьютера задачу и начинает двигаться от однопоточного режима к многопоточному, делает он это шаблонным образом. 3 шага: Компактно и доходчиво можно поглядеть, например, здесь: «Parallelism in R».

  1. делаем core-1поток,
  2. запускаем с помощью foreach,
  3. собираем ответы и получаем результат.

Основной момент, требующий внимания — обеспечить логирование в рамках потоков, чтобы иметь возможность контроля процесса. Для типичных вычислительных задач, занимающих 100% CPU и не требующих передачи большого объема входной информации, это правильный подход. Без логирования полет пойдет даже без приборов.

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

  • возможная сильная разбалансировка нагрузки на потоки;
  • требования к производительности CPU в рамках отдельной задачи могут иметь рваный характер с наличием только отдельных резких всплесков;
  • каждое отдельное вычисления может требовать значительного объема памяти на вход и выдачу результатов также немалого объема;
  • в рамках отдельной задачи может быть микс между вычислениями, работой с диском и запросом внешних систем.

Это совершенно типичный сценарий, когда в рамках процесса надо получить на вход объемное задание, прочитать данные с диска, забрать большой кусок из БД, поспрашивать внешние системы и дождаться от них ответа (классика — REST API запрос), а потом вернуть в родительский процесс N мегабайт в качестве результата.

В самых печальных случаях параллельное исполнение может оказаться более длительным, чем однопоточное. Map-reduce по пользователям, локациям, документам,  ip- адресам, датам, … (дополните сами). Все пропало? Также могут наблюдаться проблемы с нехваткой памяти. Вовсе нет.

При этом не забываем, что мы живем в рамках полного зоопарка. Рассмотрим тезисно способ радикального улучшения ситуации. Продуктивный контур на *nix, DS ноутбуки на Win*nix\MacOS, а надо, чтобы везде работало единообразно.

  1. Микрозадача: получил на вход пользователя, запросил БД, запросил 2 внешних ИС через REST, скачал и разобрал справочник с диска, провел расчет, сбросил результат на диск\БД. Пользователей, например, 10^6.
  2. Переходим к использованию пакета future и универсального адаптера doFuture.
  3.  Если отдельные задачи таковы, что в рамках отдельных задач процессорное время нужно в малом объеме (ждем ответов сторонних систем), то doFuture позволяет в одну строчку перейти от разбиения по потокам к разбиению по отдельным процессам (можно в *nix в htop поглядеть параметры запуска).
  4. Этих процессов можно создать гораздо больше чем ядер. Никакого клинча не произойдет поскольку отдельные процессы бывают в режиме ожидания большую часть времени. Но надо будет опытным путем подобрать оптимальное число процессов исходя из циклограммы типового процесса обработки.

Ускорение может быть даже больше числа доступных ядер.
Кода нет сознательно, поскольку основная задача публикации — поделиться подходом и отличным семейством пакетов future. Результат — исходная задача выполняется в разы быстрее.

Есть еще несколько маленьких нюансов за которыми тоже надо проследить:

  • каждый процесс будет потреблять память, включая получаемые и возвращаемые данные. Увеличение числа процессов кратно увеличит требования по доступной оперативной памяти.
  • doFuture использует «магию» для автоматического определения состава передаваемых в процесс переменных и пакетов, но пускать все на самотек не стоит, лучше проверить.
  • в процессах явное управление gc и явное удаление переменных с помощью rm не помешает.
  • после завершения вычислений вызывайте plan(sequential). Это закроет все процессы и освободит занимаемую ими память.
  • если необходимо передать в процесс большой объем данных рассмотрите возможность использования внешнего хранилища (диск, БД). Не забываем, что дескрипторы передать нельзя, открывать источник надо внутри самого процесса.

Проливаем свет с помощью R». Предыдущая публикация — «Бизнес-процессы в enterprise компаниях: домыслы и реальность.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть