Замена на параллельный плир с doMC
Рассмотрим стандартную сгруппированную операцию над data.frame:
library(plyr)
library(doMC)
library(MASS) # for example
nc <- 12
registerDoMC(nc)
d <- data.frame(x = c("data", "more data"), g = c("group1", "group2"))
y <- "some global object"
res <- ddply(d, .(g), function(d_group) {
# slow, complicated operations on d_group
}, .parallel = FALSE)
Тривиально воспользоваться преимуществами многоядерной установки, просто написав .parallel = TRUE
вместо. Это одна из моих любимых функций plyr.
Но из-за того, что plyr устарела (я думаю) и по существу заменена на dplyr, purrr и т. Д., Решение для параллельной обработки стало значительно более многословным:
library(dplyr)
library(multidplyr)
library(parallel)
library(MASS) # for example
nc <- 12
d <- tibble(x = c("data", "more data"), g = c("group1", "group2"))
y <- "some global object"
cl <- create_cluster(nc)
set_default_cluster(cl)
cluster_library(cl, packages = c("MASS"))
cluster_copy(cl, obj = y)
d_parts <- d %>% partition(g, cluster = cl)
res <- d_parts %>% collect() %>% ungroup()
rm(d_parts)
rm(cl)
Вы можете вообразить, как долго может длиться этот пример, учитывая, что каждый пакет и объект, который вам нужен внутри цикла, нуждается в своем cluster_*
Команда скопировать его на узлы. Непараллельный перевод plyr-dplyr - это просто dplyr::group_by
конструкция, и, к сожалению, нет краткого способа включить параллельную обработку на нем. Итак, мои вопросы:
- Это действительно предпочтительный способ перевода моего кода из plyr в dplyr?
- Какая магия происходит за кулисами в plyr, что позволяет легко включить параллельную обработку? Есть ли причина, по которой эту возможность было бы особенно трудно добавить в dplyr, и поэтому она еще не существует?
- Мои два примера принципиально отличаются с точки зрения того, как выполняется код?
1 ответ
Я не думаю, что есть один истинный "предпочтительный" способ перевода кода {plyr} в {dplyr}.
В комментариях @Aurèle проделали лучшую работу, чем когда-либо, описав связь между {plyr} и {doMC}. Одна вещь, которая произошла, - то, что стимулы немного изменились. {doMC} из Revolution Analytics (с момента покупки Microsoft). Но Хэдли, который разработал dplyr, в настоящее время работает в RStudio. Эти две компании конкурируют в пространстве IDE. Таким образом, вполне естественно, что их пакеты не предназначены для совместной игры. Единственная форма параллелизма, которую я видел сильную поддержку для выхода из RStudio - это {sparklyr}, который они сделали относительно "простым" в настройке. Но я не могу рекомендовать использовать Spark для параллельной обработки на одном компьютере.
@Aurèle снова хорошо объяснила разницу в исполнении. Ваш новый код использует кластер PSOCK, а старый код использует вилки. Форки используют режим копирования при записи для доступа к ОЗУ, поэтому параллельные процессы могут начинаться с доступа к тем же данным сразу после разветвления. Кластеры PSOCK подобны созданию новых копий R - они должны загружать библиотеки и получать явную копию данных.
Вы можете использовать шаблон как...
library(dplyr)
library(purrr)
library(future)
plan(multicore)
options(mc.cores = availableCores())
d <- data.frame(x = 1:8, g = c("group1", "group2", "group3", "group4"))
y <- "some global object"
split(d, d$g) %>%
map(~ future({Sys.sleep(5);mean(.x$x)})) %>%
map_df(~value(.x))
... с некоторым изяществом на map_df
шаг, чтобы сделать некоторую параллельную обработку. Обратите внимание, что в {purrr} ~ - это синтаксис анонимной функции, где.x - это значения, которые были сопоставлены.
Если вам нравится жить опасно, вы можете создать версию чего-то похожего, не используя {future}, используя закрытый метод в {purrr}
mcmap <- function(.x, .f, ...) {
.f <- as_mapper(.f, ...)
mclapply(.x, function(.x) {
force(.f)
.Call(purrr:::map_impl, environment(), ".x", ".f", "list")
}) %>%
map(~ .x[[1]])
}