Jak zoptymalizować mój skrypt R, aby użyć „wielordzeniowego”

15

Używam GNU R na Ubuntu-Lucid PC, który ma 4 procesory. Aby korzystać ze wszystkich 4 procesorów, zainstalowałem pakiet „r-cran-multicore”. Ponieważ w podręczniku pakietu brakuje praktycznych przykładów, które rozumiem, potrzebuję porady, jak zoptymalizować mój skrypt, aby wykorzystać wszystkie 4 procesory.

Mój zestaw danych to ramka danych (zwana P1), która ma 50 000 wierszy i 1600 kolumn. Dla każdego wiersza chciałbym obliczyć maximun, sumę i średnią. Mój skrypt wygląda następująco:

p1max <- 0
p1mean <- 0
p1sum <-0
plength <- length(P1[,1])
for(i in 1:plength){
   p1max <- c(p1max, max(P1[i,]))
   p1mean <- c(p1mean, mean(P1[i,]))
   p1sum <- c(p1sum, sum(P1[i,]))
}

Czy ktoś mógłby mi powiedzieć, jak zmodyfikować i uruchomić skrypt, aby używać wszystkich 4 procesorów?

Produnis
źródło
w powyższym programie występuje błąd: wiersz powinien być „for (i in 1: plength)”
Simon Byrne
jesteś sztywny, dzięki!
Produnis
1
czy to nie należy do StackOverflow?
R_Coholic 20.01.11
1
To należy do StackOverflow. W ogóle nie ma tu pytania o statystyki. Tylko ogólne pytanie dotyczące programowania.
JD Long

Odpowiedzi:

11

Użyj foreach i doMC . Szczegółowe wyjaśnienie można znaleźć tutaj . Twój skrypt zmieni się bardzo niewiele, linia

for(i in 1:plength){

należy zmienić na

foreach(i=1:plength) %dopar% { 

Wymagania wstępne dla każdego skryptu wielozadaniowości używającego tych pakietów to

library(foreach)
library(doMC)
registerDoMC()

Uwaga ostrożnie. Zgodnie z dokumentacją nie można tego używać w GUI.

Jeśli chodzi o twój problem, czy naprawdę potrzebujesz wielozadaniowości? Twoja data.frame zajmuje około 1,2 GB pamięci RAM, więc powinna zmieścić się w Twojej pamięci. Możesz więc po prostu użyć zastosowania:

p1smry <- apply(P1,1,summary)

Wynikiem będzie macierz ze streszczeniami każdego wiersza.

Możesz także użyć funkcji mclapply, która znajduje się w pakiecie wielordzeniowym. Wtedy twój skrypt może wyglądać następująco:

loopfun <- function(i) {
     summary(P1[i,])
}

res <- mclapply(1:nrow(P1),loopfun)

Zwróci to listę, gdzie i-ty element będzie podsumowaniem i-tego wiersza. Możesz przekonwertować go na macierz za pomocą sapply

mres <- sapply(res,function(x)x)
mpiktas
źródło
Dziękuję Ci bardzo. Masz rację, że dzięki „Apply” skrypt może zostać zoptymalizowany. Użyłem mojego skryptu jako minimalnego przykładu, aby przekazać wiadomość ... Dzięki, twoja odpowiedź jest dokładnie tym, czego szukałem !!
Produnis,
15

Masz już odpowiedź, jak używać więcej niż jednego rdzenia, ale prawdziwy problem polega na sposobie pisania pętli. Nigdy nie rozszerzaj wektora wynikowego / obiektu przy każdej iteracji pętli . Jeśli to zrobisz, zmusisz R do skopiowania wektora wynikowego / obiektu i przedłużenia go, co zajmuje cały czas. Zamiast tego należy wstępnie przydzielić wystarczającą ilość miejsca do przechowywania przed rozpoczęciem pętli i wypełnić w miarę postępów. Oto przykład:

set.seed(1)
p1 <- matrix(rnorm(10000), ncol=100)
system.time({
p1max <- p1mean <- p1sum <- numeric(length = 100)
for(i in seq_along(p1max)){
   p1max[i] <- max(p1[i,])
   p1mean[i] <- mean(p1[i,])
   p1sum[i ]<- sum(p1[i,])
}
})

   user  system elapsed 
  0.005   0.000   0.005

Lub możesz zrobić te rzeczy poprzez apply():

system.time({
p1max2 <- apply(p1, 1, max)
p1mean2 <- apply(p1, 1, mean)
p1sum2 <- apply(p1, 1, sum)
})
   user  system elapsed 
  0.007   0.000   0.006 

Pamiętaj jednak, że nie jest to szybsze niż prawidłowe wykonanie pętli, a czasem wolniejsze.

Jednak zawsze szukaj kodu wektorowego. Można zrobić sum wierszy i sposoby korzystania rowSums()i rowMeans()które są szybsze niż jednej pętli lub applywersjach:

system.time({
p1max3 <- apply(p1, 1, max)
p1mean3 <- rowMeans(p1)
p1sum3 <- rowSums(p1)
})

   user  system elapsed 
  0.001   0.000   0.002 

Gdybym był bukmacherem, miałbym pieniądze na trzecie podejście, o którym wspominam pokonanie, foreach()lub na inne wielordzeniowe opcje w teście prędkości na twojej matrycy, ponieważ musiałyby one znacznie przyspieszyć, aby uzasadnić narzut związany z konfiguracją oddzielne procesy wykonywane z różnych rdzeni procesora.

Aktualizacja: Czy po komentarzu z @shabbychef szybciej jest zrobić sumy raz i użyć ponownie w obliczeniu średniej?

system.time({
    p1max4 <- apply(p1, 1, max)
    p1sum4 <- rowSums(p1)
    p1mean4 <- p1sum4 / ncol(p1)
    })

   user  system elapsed 
  0.002   0.000   0.002

Nie w tym teście testowym, ale nie jest to wyczerpujące ...

Przywróć Monikę - G. Simpson
źródło
FWIW, Matlab ma te same problemy dotyczące wstępnej alokacji i rozszerzania wektorów i jest klasycznym „blooper”. Oprócz twojego zakładu, prawdopodobnie szybciej jest użyć wyników rowSumsdo obliczenia średnich wierszy (chyba że brakuje mi czegoś dotyczącego np. Na lub NaN). Kod w trzecim podejściu sumuje każdą kolumnę dwa razy .
shabbychef
@shabbychef będziesz zaskoczony (zobacz moją zredagowaną odpowiedź). Tak kwoty obliczane są hipotetycznie dwukrotnie, ale rowSumsi rowMeanssą wysoce zoptymalizowany kod skompilowany i co uzyskujemy jedynie obliczania sum raz, ponownie straci w ten średni obliczeń w kodzie interpretowane.
Przywróć Monikę - G. Simpson
@Gavin Simpson: nie tak szybko: spróbuj zamiast system.time({ for (iii in c(1:1000)) { p1max3 <- apply(p1, 1, max) p1mean3 <- rowMeans(p1) p1sum3 <- rowSums(p1) } })i podobnie system.time({ for (iii in c(1:1000)) { p1max4 <- apply(p1, 1, max) p1sum4 <- rowSums(p1) p1mean4 <- p1sum4 / ncol(p1) } }); wersja, która nie przelicza sumy, zajmuje 1.368 sekund na moim komputerze; ten, który zajmuje 1,396. znowu, dalekie od wyczerpującego, ale bardziej przekonującego ...
shabbychef
@shabbychef musimy mieć różne pomysły na temat tego, co jest lub nie jest przekonujące ;-) W rzeczywistości, bardziej rygorystyczne symulacje wzmocnić mój główny punkt, który jak rowMeansi rowSumsrealizowane są w wydajny i zoptymalizowany kod skompilowany mają zamiar być trudne do pokonania.
Przywróć Monikę - G. Simpson
@Gavin Simpson. W rzeczywistości problem z moim przykładem polega na tym, że większość czasu zajmuje część aplikacyjna, aby obliczyć maksimum. Zgadzam się z Tobą, że funkcja c wektorowy opartego jak rowMeanbędzie trudno pokonać za pomocą narzędzia R ogólnego przeznaczenia, takich jak *apply. Wydaje się jednak sugerować, że szybciej jest zsumować 10000 liczb dwa razy za pośrednictwem rowMeani rowSumzamiast tylko raz i użyć wbudowanego operatora podziału R. Wiem, że R ma pewne problemy z wydajnością ( np . Niedawne odkrycie nawiasów klamrowych vs. nawias), ale to wydaje się szalone.
shabbychef
1

Spójrz na śnieg i śniegu opakowaniach. Wiele przykładów z tymi ...

Jeśli chcesz przyspieszyć ten konkretny kod, zamiast uczyć się o R i równoległości, powinieneś to zrobić

P1 = matrix(rnorm(1000), ncol=10, nrow=10
apply(P1, 1, max)
apply(P1, 1, mean)
apply(P1, 1, sum)
Dr G.
źródło
pomóż mi zmodyfikować skrypt ...
Produnis,
2
Te tylko ukrywają przed tobą pętlę. Prawdziwy problem z kodem @Produnis polega na tym, że trwa wymuszone kopiowanie, ponieważ wektory wyników są rozszerzane przy każdej iteracji pętli.
Przywróć Monikę - G. Simpson
Pakiet śniegu może przedłużyć rozwiązanie Gavina, np. mówiąc „ciasto”. Pakiet ma mnóstwo funkcji modyfikowania zmodyfikowanych, aby wykonać multicoring. Aby zastosować funkcję, użyj sfApply (<twoje dokumenty jak dla zastosowania>). Opady śniegu są również dobrze udokumentowane. Powinienem zauważyć, że do wykonania tego na procesorze wielordzeniowym nie jest potrzebne żadne dodatkowe oprogramowanie. Zobacz stackoverflow.com/questions/4164960/... na przykład sfLapply.
Roman Luštrik