Comment accélérer le travail avec l'API R-language en utilisant le calcul parallèle, en utilisant l'exemple d'API Yandex.Direct (Partie 2)

Dans le dernier article, j'ai parlé de ce qu'est le multithreading et donné des exemples de son implémentation dans le langage R lors de l'utilisation de l'API Yandex.Direct en utilisant les doSNOW , doParallel et la construction foreach .


Cet article est une continuation, mais peut être considéré comme un guide hors ligne du multithreading dans R. J'ai été invité à l'écrire par les commentaires reçus dans la première partie (ici un merci spécial à Alexey_mosc , SatCat , Ananiev_Genrih ), dans lequel on m'a donné un certain nombre de packages qui représentent une approche plus moderne de les implémentations du multithreading dans R, nous en parlerons plus tard.


Multithreading


Table des matières



Défi


À titre d'exemple, nous prenons le problème considéré dans une publication précédente , à savoir en mode multi-thread, collectez une liste de mots clés à partir de 4 comptes publicitaires Yandex.Direct.


Pour travailler avec l'API Yandex.Direct, nous utiliserons le package ryandexdirect . La documentation officielle pour cela est sur le lien , mais pour la mise en œuvre de la tâche décrite, nous n'avons besoin que de 2 fonctions:


  • yadirAuth - autorisation dans l'API Yandex.Direct;
  • yadirGetKeyWords - Téléchargez une liste de mots clés à partir des comptes publicitaires.

Ce n'est pas seulement que j'ai choisi le processus de téléchargement des mots-clés, le fait est que c'est l'une des opérations les plus longues de l'API Yandex.Direct. Deuxièmement, dans tous les comptes, le nombre de mots-clés est différent, par conséquent, le temps pour terminer cette opération pour chaque compte sera très différent, dans notre cas de 1 à 20 secondes.


La préparation


Au départ, vous devez installer tous les packages décrits dans cet article, pour cela, vous pouvez utiliser le code ci-dessous.


Code 1: installation de packages
 #    install.packages("ryandexdirect") install.packages("tictoc") install.packages("rbenchmark") install.packages("dplyr") install.packages("purrr") install.packages("future") install.packages("promises") install.packages("furrr") install.packages("future.apply") 

Pour que les fonctions du package soient disponibles, vous devez le connecter à l'aide de la commande de library . Pour plus de commodité, je connecterai séparément tous les packages nécessaires dans chaque exemple de code donné.


Nous créons un vecteur composé de connexions Yandex.Direct, à partir desquelles nous demanderons plus tard des mots clés:


Code 2: création d'un vecteur de connexion
 logins <- c("login1", "login2", "login3", "login4") 

Pour travailler avec l'API Yandex.Direct, vous devez d'abord passer par une autorisation sous chaque compte, pour cela, vous pouvez utiliser la conception suivante:


Code 3: autorisation dans l'API Yandex.Direct
 lapply(logins, function(l) { yadirAuth(Login = l)}) 

Après avoir exécuté le code ci-dessus, un navigateur s'ouvrira pour autorisation sous chaque compte. Vous confirmez l'autorisation pour ryandexdirect d'accéder à votre matériel publicitaire. Vous serez redirigé vers la page où vous devez copier le code de vérification. En l'entrant dans la console R, terminez le processus d'autorisation. Cette opération est répétée pour chaque connexion que vous avez spécifiée lors de la création des connexions vectorielles.


Certains utilisateurs, au cours du processus d'autorisation, peuvent être déroutés par le fait d'une redirection vers une ressource tierce, mais il n'y a aucun danger pour votre compte à ce sujet, j'ai décrit ce sujet plus en détail dans l'article "À quel point il est sûr d'utiliser des packages R pour travailler avec des API de systèmes publicitaires" .


Ensuite, nous considérerons plusieurs exemples de mise en œuvre de la tâche décrite. Chacun commencera par un exemple de code et son explication supplémentaire. Je pense que cette option sera la plus pratique pour la perception.


Exemple de solution de traitement en série, fonction sapply et package purrr



Dans le dernier article , j'ai donné un exemple en utilisant une boucle for comme exemple. Puisque nous avons envisagé le multithreading en utilisant le package foreach , dont la syntaxe ressemble à des boucles, cet exemple était approprié ici, bien que l'utilisation de boucles ne soit pas bien accueillie par les utilisateurs de R.


Les packages que nous considérerons dans cet article rappellent davantage les fonctions de la famille apply dans la syntaxe; je vais donc donner un exemple de solution en mode série en les utilisant.


Fonction sapply


Pour estimer le temps d'exécution des commandes, dans chacune des approches considérées, nous utiliserons le package tictoc .

Code 4: Exemple de solution en mode séquentiel utilisant la fonction sapply
 library(tictoc) library(dplyr) tic() #   kw.sapply <- sapply( logins, #  ,     function(x) #        #     { yadirGetKeyWords(Login = x) %>% mutate(login = x) }, simplify = FALSE #     ) toc() #   #       result.sapply <- do.call("rbind", kw.sapply) 

39.36 sec elapsed : 39.36 sec elapsed


Au début, la syntaxe des fonctions de la famille apply n'est pas aussi facile à lire que la syntaxe des boucles, mais en fait tout est assez simple.


sapply(X, FUN)


Où:


  • X - Un objet dont nous allons parcourir les éléments et les utiliser tour à tour à chaque itération, dans une boucle for il ressemblait à ceci: for(i in X) ;
  • FUN - Une fonction dans laquelle nous substituerons chaque élément de l'objet X à son tour, si nous faisons une analogie avec for , c'est le corps de la boucle.

Dans l'exemple de code 4 , le vecteur de connexion créé précédemment est passé à l'argument X. Chaque élément du vecteur de connexion est transmis à son tour comme seul argument à la fonction de function(x) { yadirGetKeyWords(Login = x) %>% mutate(login = x) } anonyme function(x) { yadirGetKeyWords(Login = x) %>% mutate(login = x) } qui a été transmise à l'argument FUN .


C'est-à-dire sapply exécutera la fonction spécifiée dans FUN 4 fois, en y substituant les connexions une par une, et renverra le résultat sous la forme d'une liste (objet de liste de classe) composée de 4 éléments. Chaque élément est un tableau avec une liste de mots clés reçus du compte à chaque itération.


  1. yadirGetKeyWords(Login = "login1") %>% mutate(login = "login1")
  2. yadirGetKeyWords(Login = "login2") %>% mutate(login = "login2")
  3. yadirGetKeyWords(Login = "login3") %>% mutate(login = "login3")
  4. yadirGetKeyWords(Login = "login4") %>% mutate(login = "login4")

L'objet obtenu à l'aide de sapply a la structure suivante:


 summary(kw.sapply) 

  Length Class Mode login1 19 data.frame list login2 19 data.frame list login3 19 data.frame list login4 19 data.frame list 

À la fin de cet exemple, la commande result.sapply <- do.call("rbind", kw.sapply) combine les 4 éléments de la liste kw.sapply en un seul cadre result.sapply .


 # A tibble: 6,804 x 1 result.sapply$Id $Keyword $AdGroupId $CampaignId $ServingStatus $State <dbl> <fct> <dbl> <int> <fct> <fct> 1 15164230566 ~ 3597453985 39351725 ELIGIBLE ON 2 15164230567  ~ 3597453985 39351725 ELIGIBLE ON 3 15164230568  ~ 3597453985 39351725 ELIGIBLE ON 4 15164230569 ~ 3597453985 39351725 ELIGIBLE ON 5 15164230570 ~ 3597453985 39351725 ELIGIBLE ON 6 15164230571  ~ 3597453985 39351725 ELIGIBLE ON 7 15164230572 ~ 3597453985 39351725 ELIGIBLE ON 8 15164230573  ~ 3597453985 39351725 ELIGIBLE ON 9 15164230574 ~ 3597453985 39351725 ELIGIBLE ON 10 15164230575 ~ 3597453985 39351725 ELIGIBLE ON # ... with 6,794 more rows, and 13 more variables: $Status <fct>, # $StrategyPriority <fct>, $StatisticsSearchImpressions <int>, # $StatisticsSearchClicks <int>, $StatisticsNetworkImpressions <int>, # $StatisticsNetworkClicks <lgl>, $UserParam1 <chr>, $UserParam2 <chr>, # $ProductivityValue <lgl>, $ProductivityReferences <lgl>, $Bid <dbl>, # $ContextBid <dbl>, $login <chr> 

En plus de sapply , la famille de fonctions *apply comprend: apply , lapply , vapply , mapply et autres.


Forfait purrr


Code 5: Exemple de solution utilisant les fonctions du package purrr
 library(purrr) library(dplyr) library(tictoc) tic() #   result.purrr <- map_df( logins, #  ,     ~ #   function(.x) { yadirGetKeyWords(Login = .x) %>% mutate(login = .x) } ) toc() #   

35.46 sec elapsed : 35.46 sec elapsed


Le paquet purrr fait partie du noyau de la bibliothèque tidyverse , créée par Headley Wickham.


En termes de sens et de syntaxe, les principales fonctions du package sont très similaires à sapply , son principal avantage est le suivant:


  • Les fonctions sont divisées en familles map , map2 , pmap , walk etc., des fonctions distinctes incluses dans la même famille renvoient le résultat dans différents formats: chr , dbl , int , df , etc.;
  • Les fonctions de la famille map2 vous map2 d' map2 sur des éléments (itérer) simultanément de deux objets;
  • Les fonctions de la famille pmap vous pmap d' pmap simultanément sur des éléments d'un nombre quelconque d'objets. Vous pouvez passer une table à l'entrée de l'argument .l (un analogue de l'argument X dans sapply) , dont chaque colonne contiendra les valeurs par lesquelles vous itérerez, et qui seront à leur tour substituées dans les arguments de la même fonction passée en .f (l'analogue FUN de sévèrement) .

Dans quelle situation devons-nous parcourir les éléments de plusieurs objets. Par exemple, vous travaillez avec plusieurs comptes d'agent et les comptes publicitaires à partir desquels vous souhaitez obtenir une liste de mots clés sont dispersés entre eux. Dans ce cas, vous pouvez créer un vecteur à partir des noms des comptes d'agent et le parcourir, en parallèle avec la façon dont vous triez les connexions des comptes publicitaires.


Code 6: Exemple de travail avec plusieurs comptes d'agent
 library(purrr) #      agencies <- c("agency1", NA, "agency2", "agency1") #      #         result.pmap2 <- map2_df(.x = logins, .y = agencies, ~ { yadirGetKeyWords(Login = .x, AgencyAccount = .y) %>% mutate(login = .x) }) 

Imaginez maintenant la situation où lorsque vous vous êtes connecté sous différents comptes, vous avez enregistré le fichier avec les informations d'identification dans différents dossiers, puis vous devez itérer immédiatement sur trois objets: les connexions des comptes publicitaires, les connexions des comptes d'agent, le chemin dans lequel le fichier avec les informations d'identification est stocké. Cela peut être fait avec de l'aide. pmap famille pmap .


Code 7: exemple de fonction pmap
 library(purrr) #  ,       #      TokenPath <- c("C:\\proj1\\tokens", "C:\\yandex\\token", "C:\\yandex\\token", "C:\\my_yandex_acoount") #   pmap.result <- pmap_df(list(Login = logins, AgencyAccount = agencies, TokenPath = TokenPath), yadirGetKeyWords) 

Par conséquent, le résultat de l'exécution des fonctions map_df , map2_df et pmap_df est le cadre de date, et lors de leur utilisation, la dernière étape de l'exemple avec sapply ( do.call("rbind", kw.sapply) ) n'est pas requise.


Le code est devenu plus compact et exécuté un peu plus rapidement, mais néanmoins, les deux approches décrites, sapply et purrr , collectent séquentiellement les mots clés de chaque compte. Par conséquent, le temps d'exécution total de cette opération est égal à la somme des durées de collecte des données des quatre comptes.


Heure [total] = Heure [connexion1] + Heure [connexion2] + Heure [connexion3] + Heure [connexion4]


Options multithread pour résoudre la tâche de collecte de mots clés à partir de Yandex.Direct



Donc, si vous avez déjà lu le premier article , vous savez que le mode de fonctionnement multithread possède plusieurs fonctionnalités:


  • Chaque thread démarre dans une session R distincte avec un environnement de travail propre.
  • Pour la même raison, dans un processus en cours d'exécution séparé, les paquets précédemment connectés ne sont pas transmis par défaut.

L'exportation d'objets créés dans un environnement de travail et la connexion de packages dans chaque approche sont implémentées différemment, nous les examinerons plus en détail.


Paquet parallel


Ce package a d'abord été inclus dans le package R dans la version 2.14.0 et est à ce jour livré avec R lui-même.


Code 8: Exemple de solution au problème via le package parallèle
 library(parallel) library(tictoc) #   cl <- makeCluster(4) #      clusterExport(cl = cl, varlist = "logins") #  ,      #  ,       ryandexdirect clusterEvalQ(cl = cl, { library(ryandexdirect) library(dplyr) } ) tic() #   parallel.kw <- parSapplyLB(cl = cl, #   X = logins, # ,     FUN = function(x) { #      #      X yadirGetKeyWords(Login = x) %>% mutate(login = x) }, simplify = F) #    toc() #     #   stopCluster(cl) #      result.parallel <- dplyr::bind_rows(parallel.kw) 

16.75 sec elapsed : 16.75 sec elapsed


Essayons d'analyser le code 8 . La fonction makeCluster crée un cluster de 4 processus. Nous pouvons exporter des objets de notre environnement de travail principal vers le cluster créé en utilisant la fonction clusterExport , pour cela nous devons utiliser ses arguments:


  • cl - Cluster dans lequel nous allons exporter des objets
  • varlist - Un vecteur de texte contenant les noms des objets à exporter vers chaque processus de cluster.

Une façon de connecter les bons packages sur chaque nœud de cluster consiste à utiliser la fonction clusterEvalQ . Dans notre exemple, nous l'utilisons pour connecter des packages, mais vous pouvez écrire n'importe quel code R dans clusterEvalQ , et il sera lancé au début de chaque nœud de cluster. Les arguments pour cette fonction sont assez évidents, vous devez spécifier le cluster et les commandes qui y seront exécutées.


parSapplyLB est une version parallèle de la fonction sapply avec équilibrage de charge entre les nœuds de cluster, ils l'utilisent également, mais vous devez spécifier le cluster avec l'argument cl .


Également en parallel il existe d'autres versions parallélisées des fonctions de la famille *apply : parLapply , parSapply , parApply , etc.


parSapply diffère de parSapplyLB uniquement en ce qu'il n'a pas d'équilibrage de charge sur les nœuds de cluster.


La fonction stopCluster est utilisée pour arrêter le cluster créé.


La dernière commande, dplyr::bind_rows(parallel.kw) nous combinons l'objet parallel.kw obtenu en utilisant parSapplyLB dans une table.


Pour Linux, le parallel a des fonctions distinctes: mclapply , mcmapply , mcMap . Souvent, dans ce système d'exploitation, les commandes sont exécutées plus rapidement et le code devient plus compact.


Code 9: solution utilisant mclapply pour Linux
 library(parallel) library(tictic) library(dplyr) library(ryandexdirect) tic() mclapply.kw <- mclapply(logins, FUN = function(x) { #      #      X yadirGetKeyWords(Login = x) %>% mutate(login = x) }, mc.cores = 4) toc() 

Lorsque vous utilisez ces fonctions, il n'est pas nécessaire de démarrer le cluster à l'aide de la makeCluster . le nombre de nœuds que vous spécifiez à l'aide de l'argument mc.cores . Il n'est pas non plus nécessaire de connecter des packages et d'exporter des objets; ces opérations sont effectuées automatiquement.


Forfait future


L'une des approches les plus modernes de la programmation asynchrone en R.


Un code qui, en parallèle, résoudra notre problème avec l'aide du future est suffisamment compliqué à comprendre. Par conséquent, analysons son travail sur un exemple plus simple, nous demanderons une liste de mots clés à partir d'un compte.


Code 10: l'exemple le plus simple d'utilisation du futur package
 library(future) #    plan(multiprocess) #      #    future.kw <- future({yadirGetKeyWords(Login = logins[4])}, packages = "ryandexdirect", globals = "logins") #     resolved(future.kw) #     future.result.1 <- value(future.kw) 

Essayons de comprendre l'exemple du Code 10 . La fonction plan vous permet de définir et de modifier le mode d'exécution des expressions données, en voici les principales:


  • séquentiel - Il s'agit du mode de fonctionnement R habituel; les commandes sont exécutées séquentiellement dans la session en cours;
  • multisession - Mode parallèle, les commandes seront exécutées dans les sessions en cours en arrière-plan sur la machine actuelle, tandis que votre session de travail ne sera pas bloquée;
  • cluster - Mode parallèle, les commandes seront exécutées sur la machine actuelle ou distante, similaire à la façon dont elle est implémentée dans le package parallel .

L'ensemble du future package est basé sur l'exécution de commandes dans des processus en arrière-plan sans bloquer la session en cours. L'exécution de commandes suit la fonction du même nom future , donc lorsque nous exécutons la commande:


 future({yadirGetKeyWords(Login = logins[4])}, packages = "ryandexdirect", globals = "logins") 

Notre session actuelle dans R n'est pas bloquée et la commande est exécutée en arrière-plan, en exécutant une autre session R.


Vous pouvez vérifier l'état actuel du processus d'exécution d'une expression donnée à l'aide de la fonction resolved . Enfin, la fonction de value est utilisée pour obtenir le résultat d'une future exécution. Si vous exécutez la fonction de value plus tôt que votre future exécution dans une session d'exécution parallèle, la session de travail en cours sera bloquée jusqu'à la fin de l'expression de session parallèle.


L'exemple de travail le plus avancé est l'utilisation du future conjointement avec les promises .


Code 11: Exemple de partage de packages de «futures» et de «promesses»
 library(future) library(promises) #    plan(multiprocess) #      #    future.kw <- future({suppressMessages( yadirGetKeyWords(Login = logins[4]))}, packages = "ryandexdirect", globals = "logins") %...>% #     future, #      nrow() %...>% paste("words loaded") %...>% print() 

Le package promises fournit un ensemble d'opérateurs de pipelines qui complètent parfaitement les fonctionnalités future .


Dans l'exemple Code 11 , en arrière-plan, nous commençons le processus de téléchargement de mots clés à partir d'un compte publicitaire. De plus, l'opérateur de pipeline %...>% sans bloquer la session de travail attend la fin du future et effectue les opérations restantes. À la suite de l'exécution du code, à la fin des travaux future , le nombre de mots-clés du compte spécifié sera affiché dans la console:


 [1] "1855 words loaded" 

À la fin de l'article, un exemple plus illustratif d'un tas d' future et de promises sera démontré.

Par défaut, le future package exporte lui-même l'intégralité de l'espace de travail vers chaque session parallèle, mais vous pouvez vous-même spécifier une liste d'objets à exporter à l'aide de l'argument global .


Pour connecter les packages à l' future passer un vecteur contenant leurs noms à l'argument packages .


Revenons maintenant à notre tâche, l'exemple de code suivant en mode parallèle chargera une liste de mots clés à partir de 4 comptes:


Code 12: un exemple de résolution d'un problème à l'aide du futur package
 library(future) library(tictoc) #   plan("multisession", workers = 4) tic() #   futs <- lapply(logins, #      function(i) #        #   future({ yadirGetKeyWords(Login = i) %>% mutate(login = i) }, packages = c("ryandexdirect", "dplyr"))) completed <- sapply(futs, resolved) #    kw <- lapply(futs, value) #    toc() #    #     result.future <- dplyr::bind_rows(kw) 

Délai: 14.83 sec elapsed


Pour télécharger une liste de mots clés en mode multithread à partir de tous les comptes publicitaires répertoriés dans les connexions vectorielles , vous devez exécuter un future distinct en arrière-plan. Dans l'exemple de code 12, nous implémentons cela en utilisant la fonction lapply .


Le résultat de travailler lapply est une liste de future lancés. Vous pouvez vérifier le statut de chacun en utilisant la commande sapply(futs, resolved) , qui renverra un vecteur logique où TRUE signifiera que le future rempli et FALSE que le future est actuellement en cours.


Pour obtenir des résultats de chaque future , une fois leur travail terminé, nous utilisons la commande lapply(futs, value) .


: result.future <- dplyr::bind_rows(kw) .


future


, (
future ), .


future.apply

future.apply future , .


13: future.apply
 library(future.apply) library(tictoc) #    plan("multisession", workers = 4) tic() #   kw.future.apply <- future_sapply(logins, #    ,   function(x) { #     yadirGetKeyWords(Login = x) %>% mutate(login = x) }, simplify = FALSE, #    #   future.packages = c("ryandexdirect", "dplyr"), future.globals = TRUE ) toc() #    

: 17.28 sec elapsed


13 , future.apply future , .


4 : plan("multisession", workers = 4) .


future_sapply logins . C'est-à-dire , , sapply , .


future_sapply future.packages . future.globals . , .


furrr


future . purrr , furrr .


14: furrr
 library(furrr) library(tictoc) #   cl <- parallel::makeCluster(4) plan(cluster, workers = cl) tic() #   furrr.kw <- future_map(logins, ~ #   function(.x) yadirGetKeyWords(Login = .x) %>% mutate(login = .x), .options = future_options(packages = c("ryandexdirect", "dplyr"), globals = c())) toc() #    #      result.furrr <-dplyr::bind_rows(furrr.kw) 

: 15.45 sec elapsed


furrr purrr . purrr , .


.options . .options future_options , .


14 packages globals :


 .options = future_options(packages = c("ryandexdirect", "dplyr"), globals = c()) 


rbenchmark .


, future promises . .


, 20 4 () .


= (T[1] * 20) + (T[2] * 20) + (T[N] * 20)


15: future promises
 library(furrr) library(parallel) library(dplyr) library(future) library(ryandexdirect) library(tictoc) library(rbenchmark) #   logins <- c("login1", "login2", "login3", "login4") #        #        par par.furrr <- function(logins) { cl <- parallel::makeCluster(4) plan(cluster, workers = cl) furrr.kw <- future_map(logins, ~ yadirGetKeyWords(Login = .x) %>% mutate(login = .x), .options = future_options(packages = c("ryandexdirect", "dplyr"), globals = c())) result.furrr <-dplyr::bind_rows(furrr.kw) } par.future <- function(logins) { plan("multisession", workers = 4) futs <- lapply(logins, function(i) future({ yadirGetKeyWords(Login = i) %>% mutate(login = i) }, packages = c("ryandexdirect", "dplyr"))) completed <- sapply(futs, resolved) kw <- lapply(futs, value) result.future <- dplyr::bind_rows(kw) } par.future.apply <- function(logins) { plan("multisession", workers = 4) kw.future.apply <- future_sapply(logins, function(x) { yadirGetKeyWords(Login = x) %>% mutate(login = x) }, simplify = FALSE, future.packages = c("ryandexdirect", "dplyr"), future.globals = TRUE ) result.future.apply <- dplyr::bind_rows(kw.future.apply) } par.parallel <- function(logins) { cl <- parallel::makeCluster(4) clusterExport(cl = cl, varlist = "logins") clusterEvalQ(cl = cl, { library(ryandexdirect) library(dplyr) } ) parallel.kw <- parSapplyLB(cl = cl, X = logins, FUN = function(x) { yadirGetKeyWords(Login = x) %>% mutate(login = x) }, simplify = F) stopCluster(cl) result.parallel <- dplyr::bind_rows(parallel.kw) } #          seq seq.apply <- function(logins) { kw.sapply <- sapply( logins, function(x) { yadirGetKeyWords(Login = x) %>% mutate(login = x) }, simplify = FALSE ) result.sapply <- do.call("rbind", kw.sapply) } seq.purrr <- function(logins) { kw.purrr <- map_df( logins, ~ { yadirGetKeyWords(Login = .x) %>% mutate(login = .x) } ) result.purrr <- do.call("rbind", kw.purrr) } #       rbenchmark #   future + promises #  ,       #          plan(list(tweak(multisession, workers = 2), tweak(multisession, workers = 4))) tic() speed.test <- future({ #          within(benchmark(furrr = par.furrr(logins), future = par.future(logins), future.apply = par.future.apply(logins), parallel = par.parallel(logins), apply = seq.apply(logins), purrr = seq.purrr(logins), replications = c(20), columns = c('test', 'replications', 'elapsed'), order = c('elapsed', 'test')), { average = round(elapsed/replications, 2) }) }, packages = c("dplyr", "ryandexdirect", "rbenchmark", "parallel", "purrr", "future", "promises", "furrr", "future.apply"), globals = c("logins", "par.furrr", "par.future", "par.future.apply", "par.parallel", "seq.apply", "seq.purrr")) %...>% print() %...T>% toc() message("My Session is not blocked") 

3370 , .. .


. , future , promises , .


, . "My Session is not blocked", , , .. .


promises :


  • %...>%%>% , . C'est-à-dire , resolved , future , value . , print .
  • %...T>%%T>% , , . , , , .. .. print , , .
  • %...T!% — .

15 plan tweak ( plan(list(tweak(multisession, workers = 2), tweak(multisession, workers = 4))) ), , 2 , future 4 .


:


 My Session is not blocked test replications elapsed average 4 parallel 20 393.02 19.65 1 furrr 20 402.09 20.10 2 future 20 431.19 21.56 3 future.apply 20 432.29 21.61 5 apply 20 847.77 42.39 6 purrr 20 864.19 43.21 3370.55 sec elapsed 

image


, parallel , . furrr , future future.apply .


1 , , . , API . .


, 4 , .


Conclusion


R, API.


, API . " R , 1" .


:


  • doSNOW / doParallel + foreach
  • future + promises
  • future.apply / furrr
  • parallel

, , .


, R .

Source: https://habr.com/ru/post/fr448404/


All Articles