foreach %dopar% приводит к ошибкам «не удается найти функцию», когда функция, которую нужно распараллелить, является элементом списка

Я успешно распараллелил функцию — назовем ее AddOne — с помощью пакета doParallel, foreach и %dopar%, и я знаком с аргументами .packages и .export для foreach.

Моя проблема в том, что я хотел бы, чтобы AddOne вместо «автономной» функции был элементом списка, и в этом случае я не могу заставить все работать. В частности, если AddOne вызывает подпрограмму AddOneSubroutine, то AddOneSubroutine не будет найдена в «рабочей» среде, даже если она «экспортирована».

Я использую Windows 10 и R.version выдает:

platform       x86_64-w64-mingw32          
arch           x86_64                      
os             mingw32                     
system         x86_64, mingw32             
status                                     
major          3                           
minor          4.1                         
year           2017                        
month          06                          
day            30                          
svn rev        72865                       
language       R                           
version.string R version 3.4.1 (2017-06-30)
nickname       Single Candle 

У меня версия doParallel 1.0.10. Вот некоторый код, демонстрирующий проблему настолько кратко, насколько я мог.

library(doParallel)
if(!exists("Registered")){
    registerDoParallel(cores = detectCores(logical = TRUE))
    Registered = TRUE
}

AddOne<-function(x){AddOneSubroutine(x)}
AddOneSubroutine <-function(x){x+1}

MyList<-list()
MyList$f<-AddOne

# Not using parallel environments, works correctly when calling AddOne 3 times
Result1 = foreach(i = 1:3) %do% AddOne(i)
Result1

# Not using parallel environments, works correctly when calling MyList$f 3 times
Result2 = foreach(i = 1:3) %do% MyList$f(i)
Result2

# Using parallel environments, works correctly when calling AddOne 3 times,
# despite not explicitly using the .export argument to export AddOneSubroutine
Result3 = foreach(i = 1:3) %dopar% AddOne(i)
Result3

# Using parallel environments, fails when calling MyList$f with error
# "could not find function "AddOneSubroutine"", even though that function is "exported"
Result4 = foreach(i = 1:3,.export = "AddOneSubroutine") %dopar% MyList$f(i)
Result4

Что я не понимаю?


person Philip Swannell    schedule 13.09.2017    source источник


Ответы (1)


Для полной воспроизводимости везде давайте удостоверимся, что мы используем воркеры в фоновых сеансах R:

library("doParallel")
cl <- parallel::makeCluster(detectCores(logical = TRUE))
registerDoParallel(cl)

Теперь я не копался в коде бэкэнда doParallel очень подробно, поэтому я не уверен на 100%, что вызывает эту проблему. Но мы знаем, что AddOneSubroutine действительно экспортируется, что можно увидеть, если использовать foreach(..., .verbose = TRUE) или просто сделать:

AddOneSubroutine <- function(x) { x + 1 }
y <- foreach(i = 1L, .export = "AddOneSubroutine") %dopar% {
    get("AddOneSubroutine")
}
str(y)
## List of 1
##  $ :function (x)  
##   ..- attr(*, "srcref")=Class 'srcref'  atomic [1:8] 1 20 1 40 20 40 1 1
##   .. .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x2e475a0> 

Однако при вызове функции MyList$f() она не найдена, что можно подтвердить с помощью:

AddOne <- function(x) exists("AddOneSubroutine")
MyList <- list()
MyList$f <- AddOne
y <- foreach(i = 1L, .export = "AddOneSubroutine") %dopar% {
    MyList$f(i)
}
str(y)
## List of 1
##  $ : logi FALSE

Итак, почему AddOneSubroutine нет в кадрах, которые ищутся изнутри MyList$f? Это может быть связано с тем, что doParallel неправильно определяет среду для MyList$f. Обходной путь, который, кажется, работает, это следующий хак:

AddOne <- function(x) { AddOneSubroutine(x) }
y <- foreach(i = 1L) %dopar% {
    environment(MyList$f) <- environment(AddOneSubroutine)
    MyList$f(i)
}
str(y)
## List of 1
##  $ : num 2

К сожалению, это не очень аккуратно и не очень удобно.

В качестве альтернативы, бэкэнд doFuture (я автор) кажется, работает немного лучше:

library("doFuture")
registerDoFuture()
plan(multisession)

AddOneSubroutine <- function(x) { x + 1 }
AddOne <- function(x) { AddOneSubroutine(x) }
MyList <- list()
MyList$f <- AddOne

y <- foreach(i = 1L) %dopar% {
    AddOneSubroutine ## dummy guiding auto-export
    MyList$f(i)
}
str(y)
## List of 1
##  $ : num 2

PS. Меня заинтересовал ваш конкретный вариант использования, потому что в идеале AddOneSubroutine должен был экспортироваться автоматически при использовании doFuture, но этого не произошло. Я нашел исправление для этого в базовом пакете globals ( Я автор) но мне нужно больше подумать, прежде чем опубликовать это.

Мои детали:

> sessionInfo()
R version 3.4.1 (2017-06-30)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 16.04.3 LTS

Matrix products: default
BLAS: /usr/lib/atlas-base/atlas/libblas.so.3.0
LAPACK: /usr/lib/atlas-base/atlas/liblapack.so.3.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] parallel  stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
[1] doFuture_0.5.1  iterators_1.0.8 foreach_1.4.3   future_1.6.1   

loaded via a namespace (and not attached):
[1] compiler_3.4.1   tools_3.4.1      listenv_0.6.0    codetools_0.2-15
[5] digest_0.6.12    globals_0.10.2  
person HenrikB    schedule 15.09.2017