Comment optimiser mon script R pour utiliser le «multicœur»

15

J'utilise GNU R sur un PC Ubuntu-Lucid qui dispose de 4 processeurs. Afin d'utiliser les 4 CPU, j'ai installé le package "r-cran-multicore". Comme le manuel du paquet manque d'exemples pratiques que je comprends, j'ai besoin de conseils pour optimiser mon script afin d'utiliser les 4 CPU.

Mon jeu de données est un data.frame (appelé P1) qui a 50 000 lignes et 1 600 cols. Pour chaque ligne, je voudrais calculer le maximum, la somme et la moyenne. Mon script ressemble à ceci:

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,]))
}

Quelqu'un pourrait-il me dire comment modifier et exécuter le script afin d'utiliser les 4 CPU?

Produnis
la source
il y a une erreur dans le programme ci-dessus: la ligne doit être "for (i in 1: plength)"
Simon Byrne
vous êtes rigth, thx!
Produnis
1
cela n'appartient-il pas sur StackOverflow?
R_Coholic
1
Cela appartient à StackOverflow. Il n'y a aucune question statistique ici. Seulement une question de programmation générale.
JD Long

Réponses:

11

Utilisez foreach et doMC . L'explication détaillée peut être trouvée ici . Votre script va très peu changer, la ligne

for(i in 1:plength){

devrait être changé en

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

Les prérequis pour tout script multitâche utilisant ces packages sont

library(foreach)
library(doMC)
registerDoMC()

Note de prudence. Selon la documentation, vous ne pouvez pas l'utiliser dans l'interface graphique.

Quant à votre problème, avez-vous vraiment besoin du multitâche? Votre data.frame prend environ 1,2 Go de RAM, il devrait donc tenir dans votre mémoire. Vous pouvez donc simplement utiliser Apply:

p1smry <- apply(P1,1,summary)

Le résultat sera une matrice avec des résumés de chaque ligne.

Vous pouvez également utiliser la fonction mclapply qui se trouve dans le package multicore. Ensuite, votre script pourrait ressembler à ceci:

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

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

Cela renverra la liste, où le i-ème élément sera le résumé de la i-ème ligne. Vous pouvez le convertir en matrice en utilisant sapply

mres <- sapply(res,function(x)x)
mpiktas
la source
Merci beaucoup. Vous avez raison, avec "appliquer" le script pourrait être optimisé. Je viens d'utiliser mon script comme exemple minimal afin de faire passer le message ... Merci beaucoup, votre réponse est exactement ce que je cherchais !!
Produnis
15

Vous avez déjà une réponse sur la façon d'utiliser plus d'un cœur, mais le vrai problème est avec la façon dont vous avez écrit vos boucles. N'étendez jamais votre vecteur / objet résultant à chaque itération d'une boucle . Si vous faites cela, vous forcez R à copier votre vecteur / objet de résultat et à l'étendre, ce qui prend du temps. Au lieu de cela, préallouez suffisamment d'espace de stockage avant de démarrer la boucle et remplissez-la au fur et à mesure. Voici un exemple:

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

Ou vous pouvez faire ces choses via 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 

Mais notez que ce n'est pas plus rapide que de faire la boucle correctement et parfois plus lentement.

Cependant, soyez toujours à la recherche de code vectorisé. Vous pouvez faire des sommes et des moyens de ligne en utilisant rowSums()et rowMeans()qui sont plus rapides que la boucle ou les applyversions:

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

   user  system elapsed 
  0.001   0.000   0.002 

Si j'étais un homme de paris, j'aurais de l'argent sur la troisième approche que je mentionne battre foreach()ou les autres options multi-core dans un test de vitesse sur votre matrice, car ils devraient accélérer considérablement les choses pour justifier les frais généraux engagés dans la mise en place du des processus séparés qui sont développés sur les différents cœurs de CPU.

Mise à jour: Suite au commentaire de @shabbychef, est-il plus rapide de faire les sommes une fois et de les réutiliser dans le calcul de la moyenne?

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

   user  system elapsed 
  0.002   0.000   0.002

Pas dans ce test, mais c'est loin d'être exhaustif ...

Réintégrer Monica - G. Simpson
la source
FWIW, Matlab a les mêmes problèmes concernant la préallocation et l'expansion des vecteurs, et est un «bêtisier» de code classique. En plus de votre pari, il est probablement plus rapide d'utiliser les résultats de rowSumspour calculer les moyennes des lignes (sauf si je manque quelque chose concernant par exemple Na ou NaN). Le code de votre troisième approche additionne chaque colonne deux fois .
shabbychef
@shabbychef vous serez surpris (voir ma réponse modifiée). Oui, les sommes sont théoriquement calculées deux fois, mais rowSumset rowMeanssont du code compilé hautement optimisé et ce que nous gagnons en ne calculant les sommes qu'une seule fois, nous perdons à nouveau en faisant le calcul moyen en code interprété.
Rétablir Monica - G. Simpson
@Gavin Simpson: pas si vite: essayez plutôt system.time({ for (iii in c(1:1000)) { p1max3 <- apply(p1, 1, max) p1mean3 <- rowMeans(p1) p1sum3 <- rowSums(p1) } })et de la même manière system.time({ for (iii in c(1:1000)) { p1max4 <- apply(p1, 1, max) p1sum4 <- rowSums(p1) p1mean4 <- p1sum4 / ncol(p1) } }); la version qui ne recalcule pas la somme prend 1,368 seconde sur mon ordinateur; celui qui le fait prend 1,396. encore une fois, loin d'être exhaustif, mais plus convaincant ...
shabbychef
@shabbychef nous devons avoir des idées différentes sur ce qui est ou n'est pas convaincant ;-) En fait, vos simulations plus rigoureuses renforcent mon argument principal, car au fur rowMeanset à mesure qu'elles rowSumsseront implémentées dans du code compilé optimisé et efficace, elles seront difficiles à battre.
Rétablir Monica - G. Simpson
@Gavin Simpson. En fait, le problème avec mon exemple est que la plupart du temps est pris dans la partie appliquer pour calculer le maximum. Je suis d'accord avec vous qu'une fonction vectorisée basée sur c comme rowMeansera difficile à battre via un outil R à usage général comme *apply. Cependant, vous semblez suggérer qu'il est plus rapide de additionner 10000 nombres deux fois via rowMeanet rowSumplutôt qu'une seule fois et d'utiliser l'opérateur de division intégré de R. Je sais que R a des problèmes d'efficacité ( par exemple, la récente découverte du problème des accolades contre les parenthèses), mais cela semble fou.
shabbychef
1

Jetez un œil aux forfaits neige et chutes de neige . Beaucoup d'exemples avec ceux ...

Si vous voulez accélérer ce code spécifique plutôt que de vous renseigner sur R et le parallélisme, vous devriez le faire

P1 = matrix(rnorm(1000), ncol=10, nrow=10
apply(P1, 1, max)
apply(P1, 1, mean)
apply(P1, 1, sum)
Dr G
la source
merci de m'aider à modifier mon script ...
Produnis
2
Ceux-ci ne font que vous cacher la boucle. Le vrai problème avec le code @Produnis est que la copie forcée est en cours car les vecteurs de résultats sont étendus à chaque itération de la boucle.
Rétablir Monica - G. Simpson
le paquet de chutes de neige peut prolonger la solution de Gavin comme dire «gâteau». Le paquet a une pléthore de fonctions d'application modifiées pour faire du multicoring. Pour la fonction apply, vous utiliseriez sfApply (<vos arguments comme pour apply>). Les chutes de neige sont également bien documentées. Je dois souligner qu'aucun logiciel supplémentaire n'est nécessaire pour effectuer cela sur un processeur multicœur. Voir stackoverflow.com/questions/4164960/… pour un exemple sfLapply.
Roman Luštrik