Использование Rcpp в параллельном коде через снег для создания кластера

Я написал функцию на Rcpp и скомпилировал ее с помощью inline. Теперь я хочу запустить его параллельно на разных ядрах, но получаю странную ошибку. Вот минимальный пример, где функция funCPP1 может быть скомпилирована и хорошо работает сама по себе, но не может быть вызвана функцией clusterCall snow. Функция хорошо работает как отдельный процесс, но при параллельном запуске выдает следующую ошибку:

Error in checkForRemoteErrors(lapply(cl, recvResult)) : 
  2 nodes produced errors; first error: NULL value passed as symbol address

И вот немного кода:

## Load and compile
library(inline)
library(Rcpp)
library(snow)
src1 <- '
     Rcpp::NumericMatrix xbem(xbe);
     int nrows = xbem.nrow();
     Rcpp::NumericVector gv(g);
     for (int i = 1; i < nrows; i++) {
      xbem(i,_) = xbem(i-1,_) * gv[0] + xbem(i,_);
     }
     return xbem;
'
funCPP1 <- cxxfunction(signature(xbe = "numeric", g="numeric"),body = src1, plugin="Rcpp")

## Single process
A <- matrix(rnorm(400), 20,20)
funCPP1(A, 0.5)

## Parallel
cl <- makeCluster(2, type = "SOCK") 
clusterExport(cl, 'funCPP1') 
clusterCall(cl, funCPP1, A, 0.5)

person Vincent    schedule 20.05.2011    source источник


Ответы (3)


Подумайте об этом - что делает встроенный? Он создает для вас функцию C/C++, затем компилирует и связывает ее в динамически загружаемую разделяемую библиотеку. Где этот сидит? Во временном каталоге R.

Итак, вы поступили правильно, отправив интерфейс R, вызывающий эту общую библиотеку, в другой процесс (у которого есть другой временный каталог!!), но он не получает файл dll / so.

Следовательно, совет состоит в том, чтобы создать локальный пакет, установить его и загрузить и вызвать оба снежных процесса.

(И, как всегда: ответы более высокого качества могут быть в списке rcpp-devel, который читается большим количеством участников Rcpp, чем SO.)

person Dirk Eddelbuettel    schedule 20.05.2011
comment
Имеет смысл. По какой-то причине я предположил, что это связано со снегом, поэтому и написал здесь. Спасибо! - person Vincent; 20.05.2011

Старый вопрос, но я наткнулся на него, просматривая верхние теги Rcpp, так что, возможно, этот ответ все еще будет полезен.

Я думаю, что ответ Дирка является правильным, когда написанный вами код полностью отлажен и делает то, что вы хотите, но может быть сложно написать новый пакет для такого небольшого фрагмента кода, как в примере. Вместо этого вы можете экспортировать блок кода, экспортировать вспомогательную функцию, которая компилирует исходный код, и запустить вспомогательную функцию. Это сделает функцию CXX доступной, а затем используйте другую вспомогательную функцию для ее вызова. Например:

# Snow must still be installed, but this functionality is now in "parallel" which ships with base r.
library(parallel)

# Keep your source as an object
src1 <- '
     Rcpp::NumericMatrix xbem(xbe);
     int nrows = xbem.nrow();
     Rcpp::NumericVector gv(g);
     for (int i = 1; i < nrows; i++) {
      xbem(i,_) = xbem(i-1,_) * gv[0] + xbem(i,_);
     }
     return xbem;
'
# Save the signature
sig <- signature(xbe = "numeric", g="numeric")

# make a function that compiles the source, then assigns the compiled function 
# to the global environment
c.inline <- function(name, sig, src){
    library(Rcpp)
    funCXX <- inline::cxxfunction(sig = sig, body = src, plugin="Rcpp")
    assign(name, funCXX, envir=.GlobalEnv)
}
# and the function which retrieves and calls this newly-compiled function 
c.namecall <- function(name,...){
    funCXX <- get(name)
    funCXX(...)
}

# Keep your example matrix
A <- matrix(rnorm(400), 20,20)

# What are we calling the compiled funciton?
fxname <- "TestCXX"

## Parallel
cl <- makeCluster(2, type = "PSOCK") 

# Export all the pieces
clusterExport(cl, c("src1","c.inline","A","fxname")) 

# Call the compiler function
clusterCall(cl, c.inline, name=fxname, sig=sig, src=src1)

# Notice how the function now named "TestCXX" is available in the environment
# of every node?
clusterCall(cl, ls, envir=.GlobalEnv)

# Call the function through our wrapper
clusterCall(cl, c.namecall, name=fxname, A, 0.5)
# Works with my testing

Я написал пакет ctools (бесстыдная самореклама), который завершает множество функций, которые есть в пакетах parallel и Rhpc для кластерных вычислений, как с PSOCK, так и с MPI. У меня уже есть функция c.sourceCpp, которая вызывает Rcpp::sourceCpp на каждом узле почти так же, как описано выше. Я собираюсь добавить c.inlineCpp, который выполняет описанное выше, теперь, когда я вижу его полезность.

Редактировать:

В свете комментариев Коатлесса, Rcpp::cppFunction() фактически сводит на нет необходимость в помощнике c.inline, хотя c.namecall по-прежнему необходим.

src2 <- '
 NumericMatrix TestCpp(NumericMatrix xbe, int g){
        NumericMatrix xbem(xbe);
        int nrows = xbem.nrow();
        NumericVector gv(g);
        for (int i = 1; i < nrows; i++) {
            xbem(i,_) = xbem(i-1,_) * gv[0] + xbem(i,_);
        }
        return xbem;
 }
'

clusterCall(cl, Rcpp::cppFunction, code=src2, env=.GlobalEnv)

# Call the function through our wrapper
clusterCall(cl, c.namecall, name="TestCpp", A, 0.5)
person Brian Albert Monroe    schedule 05.09.2016
comment
Пожалуйста, не используйте cxxfunction. Пожалуйста, используйте cppFunction() вместо этого. - person coatless; 05.09.2016
comment
Я думаю, что это могло бы работать точно так же, я просто хотел использовать оригинальный пример. С cppFunction() кодовый блок src1 будет немного другим. Есть ли какая-то особая причина не использовать cxxfunction? - person Brian Albert Monroe; 05.09.2016
comment
Конечный результат один и тот же, но способы его достижения разные. В частности, cxxfunction() представляет код в нефункциональной форме. Используя cppFunction(), я могу написать код C++ в функциональной форме, например: Rcpp::NumericMatrix sig(Rcpp::NumericMatrix xbem, Rcpp::NumericVector gv){ int nrows = xbem.nrow(); for (int i = 1; i < nrows; i++) { xbem(i,_) = xbem(i-1,_) * gv[0] + xbem(i,_); } return xbem; }. Основное внимание уделяется фактическим расчетам, а не отслеживанию входных данных или приведению объектов. - person coatless; 05.09.2016
comment
По сути, многое изменилось с момента появления атрибутов Rcpp. Обидно видеть, что люди ими не пользуются. - person coatless; 05.09.2016
comment
Хорошая точка зрения. Лично я предпочитаю работать с отдельными исходными файлами и использовать sourceCpp(), поэтому мне никогда не приходилось проводить различие между двумя встроенными функциями. Ответ обновлен с использованием cppFunction(). - person Brian Albert Monroe; 05.09.2016
comment
Для тех, кто придет позже, я решил написать об этом пост... thecoatlessprofessor.com/programming/rcpp/ - person coatless; 18.12.2016

Я решил эту проблему, найдя на каждом узле кластера файл R с требуемой встроенной функцией C:

clusterEvalQ(cl, 
    {
     library(inline)
     invisible(source("your_C_func.R"))
    })

И ваш файл your_C_func.R должен содержать определение функции C:

c_func <- cfunction(...)
person Cristina Bazzano    schedule 22.03.2019