Юлия пмап производительность

Я пытаюсь перенести часть своего R-кода на Джулию; В основном я переписал следующий код R в Джулии:

library(parallel)

eps_1<-rnorm(1000000)
eps_2<-rnorm(1000000)

large_matrix<-ifelse(cbind(eps_1,eps_2)>0,1,0)
matrix_to_compare = expand.grid(c(0,1),c(0,1))
indices<-seq(1,1000000,4)
large_matrix<-lapply(indices,function(i)(large_matrix[i:(i+3),]))

function_compare<-function(x){
  which((rowSums(x==matrix_to_compare)==2) %in% TRUE)
}

> system.time(lapply(large_matrix,function_compare))
   user  system elapsed 
 38.812   0.024  38.828 
> system.time(mclapply(large_matrix,function_compare,mc.cores=11))
   user  system elapsed 
 63.128   1.648   6.108 

Как можно заметить, я получаю значительное ускорение при переходе с одного ядра на 11. Теперь я пытаюсь сделать то же самое в Джулии:

#Define cluster:

addprocs(11);

using Distributions;
@everywhere using Iterators;
d = Normal();

eps_1 = rand(d,1000000);
eps_2 = rand(d,1000000);


#Create a large matrix:
large_matrix = hcat(eps_1,eps_2).>=0;
indices = collect(1:4:1000000)

#Split large matrix:
large_matrix = [large_matrix[i:(i+3),:] for i in indices];

#Define the function to apply:
@everywhere function function_split(x)
    matrix_to_compare = transpose(reinterpret(Int,collect(product([0,1],[0,1])),(2,4)));
    matrix_to_compare = matrix_to_compare.>0;
    find(sum(x.==matrix_to_compare,2).==2)
end

@time map(function_split,large_matrix )
@time pmap(function_split,large_matrix )

   5.167820 seconds (22.00 M allocations: 2.899 GB, 12.83% gc time)
   18.569198 seconds (40.34 M allocations: 2.082 GB, 5.71% gc time)

Как можно заметить, я не получаю никакой скорости с pmap. Может кто подскажет альтернативы.


person Vitalijs    schedule 05.07.2016    source источник
comment
large_matrix это 250000-element Array{Any,1}: Может проблема в этом?   -  person daycaster    schedule 05.07.2016
comment
Я действительно не знаю, я очень новичок в Джулии   -  person Vitalijs    schedule 05.07.2016
comment
На Джулии 0.4.6 я получаю следующие результаты с addprocs(3): 4.173674 seconds (22.97 M allocations: 2.943 GB, 14.57% gc time) и 0.795733 seconds (292.07 k allocations: 12.377 MB, 0.83% gc time). Также тип large_matrix равен Array{BitArray{2},1}.   -  person tim    schedule 06.07.2016
comment
Это очень странно на моем macbook pro с addprocs(3) : 5.860692 seconds (22.90 M allocations: 2.938 GB, 13.20% gc time); @time pmap(function_split,large_matrix ) 27.411076 seconds (40.60 M allocations: 2.094 GB, 3.17% gc time)   -  person Vitalijs    schedule 06.07.2016
comment
Извините, я просто скопировал ваш код перед вашим редактированием и не проверил возвращаемое значение pmap. Так что это было быстро только потому, что собирало исключения. Используя новую версию, я вижу то же поведение, что и вы. Это связано с тем, что вызов вашей функции в массивах 4x2 выполняется очень быстро. pmap полезен только в том случае, если каждый вызов функции занимает значительное время. В зависимости от того, что вы хотите сделать с результирующим массивом, вам может быть интересен @parallel.   -  person tim    schedule 06.07.2016
comment
Я пробовал @parallel, но почему-то не мог понять, как применить его в цикле, в основном я просто хочу сохранить результат в каком-то массиве (или на языке R я хочу получить список списков)   -  person Vitalijs    schedule 06.07.2016


Ответы (1)


Я думаю, что часть проблемы здесь заключается в том, что @parallel и @pmap не всегда хорошо обрабатывают перемещение данных в рабочие процессы и обратно. Таким образом, они, как правило, лучше всего работают в ситуациях, когда то, что вы выполняете, вообще не требует очень большого перемещения данных. Я также подозреваю, что, вероятно, есть вещи, которые можно было бы сделать для улучшения их производительности, но я не уверен в деталях.

В ситуациях, когда вам нужно перемещать больше данных, может быть лучше придерживаться опций, которые напрямую вызывают функции для воркеров, а затем эти функции обращаются к объектам в пространстве памяти этих воркеров. Ниже я привожу один пример, который ускоряет вашу функцию с помощью нескольких воркеров. Он использует, пожалуй, самый простой вариант — @everywhere, но @spawn, remotecall() и т. д. тоже стоит рассмотреть, в зависимости от вашей ситуации.

addprocs(11);

using Distributions;
@everywhere using Iterators;
d = Normal();

eps_1 = rand(d,1000000);
eps_2 = rand(d,1000000);

#Create a large matrix:
large_matrix = hcat(eps_1,eps_2).>=0;
indices = collect(1:4:1000000);

#Split large matrix:
large_matrix = [large_matrix[i:(i+3),:] for i in indices];

large_matrix = convert(Array{BitArray}, large_matrix);

function sendto(p::Int; args...)
    for (nm, val) in args
        @spawnat(p, eval(Main, Expr(:(=), nm, val)))
    end
end

getfrom(p::Int, nm::Symbol; mod=Main) = fetch(@spawnat(p, getfield(mod, nm)))

@everywhere function function_split(x::BitArray)
    matrix_to_compare = transpose(reinterpret(Int,collect(product([0,1],[0,1])),(2,4)));
    matrix_to_compare = matrix_to_compare.>0;
    find(sum(x.==matrix_to_compare,2).==2)
end


function distribute_data(X::Array, WorkerName::Symbol)
    size_per_worker = floor(Int,size(X,1) / nworkers())
    StartIdx = 1
    EndIdx = size_per_worker
    for (idx, pid) in enumerate(workers())
        if idx == nworkers()
            EndIdx = size(X,1)
        end
        @spawnat(pid, eval(Main, Expr(:(=), WorkerName, X[StartIdx:EndIdx])))
        StartIdx = EndIdx + 1
        EndIdx = EndIdx + size_per_worker - 1
    end
end

distribute_data(large_matrix, :large_matrix)


function parallel_split()
    @everywhere begin
        if myid() != 1
            result = map(function_split,large_matrix );
        end
    end
    results = cell(nworkers())
    for (idx, pid) in enumerate(workers())
        results[idx] = getfrom(pid, :result)
    end
    vcat(results...)
end

## results given after running once to compile
@time a = map(function_split,large_matrix); ## 6.499737 seconds (22.00 M allocations: 2.899 GB, 13.99% gc time)
@time b = parallel_split();  ## 1.097586 seconds (1.50 M allocations: 64.508 MB, 3.28% gc time)

julia> a == b
true

Примечание: даже при этом ускорение от нескольких процессов не идеально. Но этого следовало ожидать, так как в результате вашей функции все еще должен быть возвращен умеренный объем данных, и эти данные должны быть перемещены, что требует времени.

P.S. См. этот пост (Джулия: как скопировать данные в другой процессор в Julia) или этот пакет (https://github.com/ChrisRackauckas/ParallelDataTransfer.jl) для получения дополнительной информации о функциях sendto и getfrom, которые я использовал здесь.

person Michael Ohlrogge    schedule 23.07.2016