Snow

Le package snow (Simple Network Of Workstations) offre la possibilité d'exécuter du code R en parallèle sur plusieurs ordinateurs. Les calculs statistiques lourds peuvent ainsi être accélérés de manière significative.


Configuration

Attention.png

Avant d'entreprendre des simulations numériques sur les machines du Département, vous êtes encouragé à consulter la page Simulations, en particulier la section traitant des consignes à respecter.

Ci-dessous, vous trouverez les instructions pour la configuration initiale de snow pour votre compte au DMS.

  • Tapez
makeKey.sh

dans un terminal de commandes. La ligne précédente exécute le script makeKey.sh qui configure le système ssh afin que vous puissiez vous brancher à d'autres machines du Département sans qu'on vous demande votre mot de passe.

  • Si la question
Generating public/private rsa key pair.
/home/bayani/.ssh/id_rsa already exists.
Overwrite (y/n)? 

apparaît, répondez par « y ».

  • Appuyez sur Entrée (Enter) lorsque vous apercevez les messages
Enter passphrase (empty for no passphrase): 

et

Enter same passphrase again:

Pour démarrer R dans un terminal, tapez

R 

pour la version non-graphique (ligne de commandes), ou encore

xR

pour la version à interface graphique.

  • Installation de snow sur votre compte DMS : À la première utilisation, snow n'est probablement pas intallé. Pour vérifier, appelez dans R
require(snow)

et si snow n'est pas installé, un message d'erreur s'affichera

Le chargement a nécessité le package : snow
Warning message:
In library(package, lib.loc = lib.loc, character.only = TRUE, logical.return = TRUE,  :
  aucun package nommé ‘snow’ n'est trouvé

Vous pouvez aussi appeler library() et parcourir la liste pour voir si snow s'y trouve.

L'installation se fait en appelant

install.packages("snow")

Ensuite R vous demandera si vous voulez installer snow dans une librairie personnelle et s'il peut l'installer dans un certain répertoire (probablement dans /home/usager/R/x86_64-pc-linux-gnu-library ). Répondez oui aux deux questions. Vous devrez par la suite choisir un miroir CRAN; prenez-en un près d'où vous êtes (probablement USA (NY)). L'installation devrait se terminer après quelques instants. Vous pouvez maintenant appeler snow par require(snow).

Exemple

L'exemple qui suit a pour but de montrer comment utiliser les fonctions du package snow afin de lancer des calculs en parallèle. Les calculs sont d'abord effectués localement, c'est-à-dire sur la machine de l'utilisateur, puis effectués par plusieurs ordinateurs (cluster).

Pour tester l'exemple par vous-même, « copiez-collez » les commandes suivantes dans R :

require(snow)

# La fonction myfunc définie ci-dessous exécute en boucle de courts calculs. Il est 
# important de noter que les itérations de la boucle sont indépendantes les unes des 
# autres : c'est ce qui permet la parallélisation.

myfunc <- function(M=1000) {
  decision <- 0
  for (i in 1:M) {
    x <- rnorm(100)
    if (shapiro.test(x)$p < 0.05) decision <- decision + 1
  }
  return(decision)
}

# Ci-dessous, on chronomètre le temps d'exécution de la fonction "myfunc".

system.time({
  M <- 6000000
  decision <- myfunc(M)
  print(decision/M)
})

# Temps d'exécution : 814 s (~ 14 min.).

# Les calculs sont maintenant divisés sur plusieurs ordinateurs (10 dans cet exemple).
# Avant de lancer les calculs, le script "snowdms.sh" est appelé afin de dresser une 
# liste de machines à utiliser pour les calculs.

nbcpus <- 10
cpus <- strsplit(system(paste("snowdms.sh ",nbcpus),intern=TRUE),split=" ")[[1]]
write(cpus, file="~/cpus.csv", 10, append=TRUE, "\t")   # La liste des machines (cpus) est enregistrée dans "cpus.csv".

system.time({
  # Fonction pour démarrer un "cluster" snow.
  cl <- makeCluster(cpus, type = "SOCK")  
  # Initialisation d'un flux de nombres aléatoires pour le cluster.              
  clusterSetupSPRNG(cl)                                 
  M <- 6000000                   

  # La fonction "myfunc" est appelée sur chaque machine de la liste, avec 
  # round(M/nbcpus) comme argument. "out[[i]]" contient les résultats
  out <- clusterCall(cl, myfunc, round(M/nbcpus))       
  # obtenus par la i-ème machine.                                                        

  # Arrêt du cluster.
  stopCluster(cl)                                       
  decision <- 0
  for (cpus in 1:nbcpus) {
    decision <- decision + out[[cpus]]
  }
  print(decision/(round(M/nbcpus)*nbcpus))
})

# Quelques temps obtenus selon le nb de cpus

# nbcpus <- 10 : 111 s
# nbcpus <- 15 : 84 s
# nbcpus <- 20 : 70 s
# nbcpus <- 80 : 58 s

# Les calculs s'effectuent plus rapidement avec un grand nombre de machines.

Remarques

  • Des messages tels que
The authenticity of host 'leopard (132.204.53.53)' can't be established.
RSA key fingerprint is a6:a3:80:c8:52:22:d8:de:be:5a:d8:f4:04:cf:2c:01.
Are you sure you want to continue connecting (yes/no)?

apparaissent lors d'une première connexion à une machine. Répondez par « yes » pour mémoriser la clé d'identification de la machine.

  • La liste des machines choisies par le script snowdms.sh est enregistrée dans un fichier dans le compte de l'usager (~/cpus.csv). Ainsi, si vous avez démarrées des simulations par erreur, vous savez sur quelles machines vous brancher (par ssh) afin de stopper ces simulations (commande kill).
  • La fonction clusterCall ne permet pas de faire varier l'argument passé à la fonction : chaque machine de la liste exécutera la même fonction avec le même argument. D'autres fonctions, comme clusterApply par exemple, offrent la possibilité de varier l'argument. Pour plus de détails, consultez le manuel du package.
  • Lorsque le package snow est utilisé pour paralléliser une fonction sur n CPUs, il faut imaginer que n nouvelles sessions de R indépendantes sont ainsi démarrées. Il est donc nécessaire d'inclure la définition de toutes les fonctions à employer à l'intérieur de la fonction parallélisée. Ceci est aussi vrai pour les packages qu'il convient de relancer avec library() (ou encore require()). Finalement, une situation similaire se produit pour le répertoire actif (working directory), puisque celui par défaut sera utilisé pour chaque nouvelle session. Dans le doute, utilisez les chemins complets.

Nombre de CPUs

Le rendement en terme de temps de calcul dépend du type de processus que vous lancez sur les machines.

Dans l'exemple ci-haut, on observe que plus on utilise de CPUs, plus vite s'effectuent les calculs. Mais il faut faire attention puisque, d'un calcul à l'autre, le gain risque de changer. En d'autres termes, les calculs ne sont pas toujours facilement parallélisables.

Il ne faut pas oublier que l'envoi de calculs sur plusieurs machines prend du temps (communication / transfert), donc il faut préalablement vous assurer que l'effort en vaille la chandelle : effectuez quelques tests d'abord avant de démarrer vos calculs lourds. Parfois, il est préférable de lancer un programme qui n'est pas trop lourd sur une seule machine.

Exemple de tous les jours

Disons qu'on veut écrire une lettre de deux lignes. À la place de l'écrire, vous vous déplacer 3 étages du Pavillon André-Aisenstadt pour aller voir votre ami au troisième étage pour lui demander de l'aide. Pendant ce temps-là vous auriez pu avoir déjà écrit votre lettre de deux lignes. Mais, par exemple si vous avez une lettre de 20 page à écrire, en ce moment-là cela vaut la peine de se déplacer trois étages pour allez demander de l'aide à son ami.

Génération de nombres aléatoires

Lorsque vous générez des nombres aléatoires pour plusieurs machines, vous devez vous assurez que chaque machine emploie une suite de nombres différente, sans quoi les résultats de toutes les machines seront identiques.

Pour ce faire, vous pourriez être tentés de procéder comme suit :

# Génération de trois nombres aléatoires avec "runif".

clusterCall(cl, runif, 3)

[[1]]
[1] 0.08496597 0.35232298 0.60300751

[[2]]
[1] 0.08496597 0.35232298 0.60300751

Mais, comme on l'aperçoit, la suite générée est identique sur toutes les machines (ici cl est configuré pour 2 machines).

Pour régler ce problème, la démarche à suivre est d'utiliser la fonction clusterSetupSPRNG prévue à cet effet, comme dans l'exemple ci-haut. Avec cette fonction, les suites générées par les machines sont différentes :

# Génération de trois nombres aléatoires avec "runif" et "clusterSetupSPRNG".

clusterSetupSPRNG(cl)
clusterCall(cl, runif, 3)

[[1]]
[1] 0.749391854 0.007316102 0.152742874

[[2]]
[1] 0.8424790 0.8896625 0.2256776

Pour plus de détails, consultez les références externes ci-dessous.

Voir aussi

Articles connexes

Références externes


La dernière modification de cette page a été faite le 3 décembre 2020 à 21:54.