Повышение производительности циклов с помощью распараллеливания - PullRequest
3 голосов
/ 09 июля 2020

Итак, я пытаюсь осмыслить возможности распараллеливания Джулии. Я моделирую сточасти c процессов как цепи Маркова. Поскольку цепи являются независимыми репликами, внешние циклы независимы, что делает проблему смущающе параллельной . Я попытался реализовать решение @distributed и @threads, оба из которых, кажется, работают нормально, но не быстрее , чем последовательное.

Вот упрощенная версия моего кода (последовательно):

function dummy(steps = 10000, width = 100, chains = 4)
    out_N = zeros(steps, width, chains)
    initial = zeros(width)
    for c = 1:chains
        # print("c=$c\n")
        N = zeros(steps, width)
        state = copy(initial)
        N[1,:] = state
        for i = 1:steps
            state = state + rand(width)
            N[i,:] = state
        end
        out_N[:,:,c] = N
    end
    return out_N
end

Каким будет правильный способ распараллеливания этой проблемы для повышения производительности?

Ответы [ 2 ]

2 голосов
/ 11 июля 2020

Вот правильный способ сделать это (на момент написания этого ответа другой ответ не работал - см. Мой комментарий).

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

1. Непараллельная версия (базовый сценарий)

using Random
const m = MersenneTwister(0);

function dothestuff!(out_N, N, ic, m)
    out_N[:, ic] .= rand(m, N)
end

function dummy_base(m=m, N=100_000,c=256)
    out_N = Array{Float64}(undef,N,c)
    for ic in 1:c
        dothestuff!(out_N, N, ic, m)
    end
    out_N 
end

Тестирование:

julia> using BenchmarkTools; @btime dummy_base();
  106.512 ms (514 allocations: 390.64 MiB)

2. Распараллеливание с потоками

#remember to run before starting Julia:
# set JULIA_NUM_THREADS=4
# OR (Linux)
# export JULIA_NUM_THREADS=4

using Random

const mt = MersenneTwister.(1:Threads.nthreads());
# required for older Julia versions, look still good in later versions :-)

function dothestuff!(out_N, N, ic, m)
    out_N[:, ic] .= rand(m, N)
end
function dummy_threads(mt=mt, N=100_000,c=256)
    out_N = Array{Float64}(undef,N,c)
    Threads.@threads for ic in 1:c
        dothestuff!(out_N, N, ic, mt[Threads.threadid()])
    end
    out_N 
end

Давайте проверим производительность:

julia> using BenchmarkTools; @btime dummy_threads();
  46.775 ms (535 allocations: 390.65 MiB)

3. Распараллеливание с процессами (на одной машине)

using Distributed

addprocs(4) 

using Random, SharedArrays
@everywhere using Random, SharedArrays, Distributed
@everywhere Random.seed!(myid())

@everywhere function dothestuff!(out_N, N, ic)
    out_N[:, ic] .= rand(N)
end
function dummy_distr(N=100_000,c=256)
    out_N = SharedArray{Float64}(N,c)
    @sync @distributed for ic in 1:c
        dothestuff!(out_N, N, ic)
    end
    out_N 
end

Производительность (обратите внимание, что межпроцессное взаимодействие занимает некоторое время и, следовательно, для небольших вычислений потоки обычно будут лучше):

julia> using BenchmarkTools; @btime dummy_distr();
  62.584 ms (1073 allocations: 45.48 KiB)
1 голос
/ 10 июля 2020

Вы можете использовать макрос @distributed для параллельного запуска процессов

@everywhere using Distributed, SharedArrays

addprocs(4)

@everywhere function inner_loop!(out_N, chain_number,steps,width)
    N = zeros(steps, width)
    state = zeros(width)
    for i = 1:steps
        state .+= rand(width)
        N[i,:] .= state
    end
    out_N[:,:,chain_number] .= N
    nothing
end

function dummy(steps = 10000, width = 100, chains = 4)
    out_N = SharedArray{Float64}((steps, width, chains); pids = collect(1:4))
    @sync for c = 1:chains
        # print("c=$c\n")
        @spawnat :any inner_loop!(out_N, c, steps,width)
    end
    sdata(out_N)
end
...