2017-02-21 26 views
4

Я разработал пакет R, который содержит функции embarassingly parallel.Какова наилучшая практика для выполнения функций в моем R-пакете?

Я хотел бы реализовать параллелизацию для этих функций таким образом, чтобы он был прозрачным для пользователя, независимо от его/ее ОС (по крайней мере, в идеале).

Я осмотрел, чтобы посмотреть, как импортировали другие авторы пакета foreach-based Parallelism. Например, пакет caret Max Kuhn импортирует foreach, чтобы использовать %dopar%, но relies, чтобы указать параллельный бэкэнд. (Несколько примеров использования doMC, который не работает на Windows).

отмечая, что doParallel работы для ОС Windows и Linux/OSX и использует встроенный в parallel упаковке (см комментарии here за полезное обсуждение), имеет смысл для импорта doParallel и выполняете ли мои функции registerDoParallel() всякий раз, когда пользователь задает parallel=TRUE в качестве аргумента?

ответ

5

Я думаю, что очень важно разрешить пользователю регистрировать свои собственные параллельные бэкэнд. Бэкэнт doParallel очень портативен, но что, если они хотят запустить вашу функцию на нескольких узлах кластера? Что делать, если они хотят установить опцию «outfile» makeCluster? Прискорбно, что прозрачность параллельной поддержки также делает ее бесполезной для многих ваших пользователей.

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

Вот пример:

library(doParallel) 
parfun <- function(n=10, parallel=FALSE, 
        cores=getOption('mc.cores', 2L)) { 
    if (parallel) { 
    # honor registration made by user, and only create and register 
    # our own cluster object once 
    if (! getDoParRegistered()) { 
     cl <- makePSOCKcluster(cores) 
     registerDoParallel(cl) 
     message('Registered doParallel with ', 
       cores, ' workers') 
    } else { 
     message('Using ', getDoParName(), ' with ', 
       getDoParWorkers(), ' workers') 
    } 
    `%d%` <- `%dopar%` 
    } else { 
    message('Executing parfun sequentially') 
    `%d%` <- `%do%` 
    } 

    foreach(i=seq_len(n), .combine='c') %d% { 
    Sys.sleep(1) 
    i 
    } 
} 

Это написано так, что он работает только параллельно, если parallel=TRUE, даже если они зарегистрировали параллельный бэкенд:

> parfun() 
Executing parfun sequentially 
[1] 1 2 3 4 5 6 7 8 9 10 

Если parallel=TRUE и они не имеют зарегистрировал бэкэнд, затем он создаст и зарегистрирует объект кластера для них:

> parfun(parallel=TRUE, cores=3) 
Registered doParallel with 3 workers 
[1] 1 2 3 4 5 6 7 8 9 10 

Если parfun вызывается с parallel=TRUE снова, он будет использовать ранее зарегистрированную кластер:

> parfun(parallel=TRUE) 
Using doParallelSNOW with 3 workers 
[1] 1 2 3 4 5 6 7 8 9 10 

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


Обратите внимание, что выбор номера по умолчанию ядер/рабочие также сложный вопрос, и один, что CRAN Сопровождающие небезразличен. Вот почему я не сделал число ядер по умолчанию detectCores(). Вместо этого я использую метод, используемый mclapply, хотя, возможно, следует использовать другое имя опции.


Относительно stopCluster

Обратите внимание, что этот пример будет иногда создать новый объект кластера, но он никогда не останавливает его через вызов stopCluster. Причина в том, что создание объектов кластера может быть дорогостоящим, поэтому я хотел бы повторно использовать их для нескольких циклов foreach, а не создавать и уничтожать их каждый раз. Однако я бы предпочел оставить это для пользователя, однако в этом примере пользователю не удастся это сделать, поскольку они не имеют доступа к переменной cl.

Есть три способа справиться с этим:

  • Вызов stopCluster в parfun когда makePSOCKcluster называется;
  • Напишите дополнительную функцию, которая позволяет пользователю остановить неявно созданный объект кластера (что эквивалентно функции stopImplicitCluster в пакете doParallel);
  • Не беспокойтесь о неявно созданном объекте кластера.

Я бы выбрал второй вариант для своего собственного кода, но это значительно усложнило бы этот пример. Это уже довольно сложно.

+0

Это похоже на правильный баланс поддержки для простой, встроенной параллелизации и поддержки для более продвинутых пользователей. Большое спасибо за ваше время. – C8H10N4O2

+0

Как бы вы остановили Cluster (cl), то? Оставить его пользователю? – JPMac

+0

@JPMac Как насчет 'on.exit (stopCluster (cl))' после 'cl <- makePSOCKcluster (ядра)' – Marek

1

Как автор будущего пакета, я рекомендую вам посмотреть на него. Будущий пакет объединяет все параллельные/кластерные функции параллельного интерфейса в один API.

https://cran.r-project.org/package=future

Он разработан таким образом, что вы, как разработчик написать свой код один раз и пользователь решает, на заднем конце, например, plan(multiprocess), plan(cluster, workers = c("n1", "n3", "remote.server.org")) и т.д.

Если пользовательского Ha сек доступ к кластеру HPC с одним из общих планировщиков, таких как Слерма, МОМЕНТ/PBS, и СГЭ, то они могут использовать пакет future.BatchJobs, который реализует в будущем API на top of BatchJobs, например plan(batchjobs_slurm). Ваш код остается прежним. (Скоро также будет пакет future.batchtools поверх batchtools)).