2017-12-01 165 views
9

Rozważmy standardu pogrupowane operację na data.frame:Zamiennik dla równoległego plyr z doMC

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) 

To trywialne, aby skorzystać z konfiguracji multi-core, po prostu pisząc .parallel = TRUE zamiast. To jedna z moich ulubionych cech plyr.

Ale z plyr są przestarzałe (chyba) i zasadniczo zastąpiony przez dplyr, Purrr itp rozwiązanie równoległe przetwarzanie stało się znacznie bardziej gadatliwy:

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) 

Można sobie wyobrazić, jak długo tego przykładem może weź pod uwagę każdy pakiet i obiekt, który potrzebujesz w pętli, i potrzebujesz jego własnej komendy cluster_*, aby skopiować ją do węzłów. Nierównoległe tłumaczenie plyr-to-dplyr jest po prostu prostą konstrukcją dplyr::group_by i niefortunne jest to, że nie istnieje prosty sposób na umożliwienie przetwarzania równoległego. Tak, moje pytania są:

  • Czy jest to w rzeczywistości preferowany sposób tłumaczenia mojego kodu z plyr na dplyr?
  • Jaki rodzaj magii dzieje się za kulisami w plyr, co sprawia, że ​​tak łatwo włączyć przetwarzanie równoległe? Czy istnieje powód, dla którego ta zdolność byłaby szczególnie trudna do dodania do dplyra i dlatego jeszcze nie istnieje?
  • Czy moje dwa przykłady różnią się zasadniczo pod względem sposobu wykonania kodu?
+3

Re swoim trzecim pytaniu: ja Powiedz tak. Twój przykład "plyr" używa 'doMC', czyli backendu' multicore' dla 'foreach', czyli: ** forking **. Twój przykład 'multidplyr' używa' create_cluster', który domyślnie stosuje 'parallel :: makePSOCKcluster', czyli: ** Parallel SOCKet Cluster **. –

+1

Na drugie pytanie: ten sam rodzaj magii, który się dzieje, jeśli po prostu wywołasz 'partition()' bez wcześniejszego skonfigurowania klastra: 'plyr' opiera się na wcześniej zarejestrowanym backend' foreach' (print (plyr ::: setup_parallel)) '),' multidplyr :: partition() 'bez klastra opiera się na' create_cluster() 'niejawnie, ale prawdopodobnie wykryje inny backend, jeśli jest już zarejestrowany (nie sprawdziłem, jednak zobacz' print() multidplyr ::: cluster_exists)) '). Pierwsze przykłady winiety 'multidplyr' ilustrują tę możliwość po prostu wywoływania' partition() 'bez wcześniejszej konfiguracji. –

+1

Na pierwsze pytanie: na tyle, na ile mogę stwierdzić, z dokumentu i z moich własnych eksperymentów, 'multidplyr' nie pozwala na rozwidlenie drogi' plyr', tylko 'PSOCK'. –

Odpowiedz

3
  1. Nie sądzę, istnieje jeden prawdziwy 'preferowany' sposób tłumaczyć {plyr} kod do {dplyr}.

  2. W komentarzach @ Aurèle wykonała lepszą pracę niż kiedykolwiek w opisywaniu połączenia między {plyr} a {doMC}. Jedno, co się stało, to, że zachęty nieco się zmieniły. {DoMC} pochodzi z Revolution Analytics (od momentu zakupu przez Microsoft). Ale Hadley, który opracował dplyr, obecnie pracuje w RStudio. Te dwie firmy konkurują w przestrzeni IDE. Być może jest to naturalne, że ich pakiety nie są tak zaprojektowane, aby dobrze się ze sobą współpracować. Jedyną formą paralelizmu, w której widziałem silne wsparcie dla wychodzenia z RStudio, jest {sparklyr}, co ułatwiają konfigurację. Ale nie mogę naprawdę polecić futury z Spark do równoległego przetwarzania dla pojedynczej maszyny.

  3. @ Aurèle ponownie wykonała dobrą robotę wyjaśniając różnice w wykonaniu. Twój nowy kod używa klastra PSOCK i starego kodu używanych wideł. Widły używają kopii w trybie zapisu do uzyskiwania dostępu do pamięci RAM, więc procesy równoległe mogą zaczynać się od dostępu do tych samych danych natychmiast po rozwidleniu. Klastry PSOCK są jak tworzenie nowych kopii R - muszą ładować biblioteki i otrzymywać jawną kopię danych.

Można użyć wzoru jak ...

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)) 

... z jakiejś finezji po kroku map_df zrobić kilka przetwarzania równoległego. Zauważ, że pod {purrr} ~ jest anonimową składnią funkcji, gdzie .x to wartości, które zostały zmapowane.

Jeśli lubisz żyć niebezpiecznie, może być w stanie stworzyć wersję coś podobnego bez użycia {} przyszłość za pomocą prywatnej metody w {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]]) 
} 
+0

Dzięki za wyjaśnienie. Jeszcze nie próbowałem kodu, ale purrr + future może być dobrym rozwiązaniem. – Devin