Julia pmap speed - параллельная обработка - динамическое программирование - PullRequest
0 голосов
/ 29 июня 2018

Я пытаюсь ускорить заполнение матрицы для задачи динамического программирования в Julia (v0.6.0), и я не могу получить дополнительную скорость от использования pmap. Это связано с вопросом, который я написал почти год назад: Заполнение матрицы с помощью параллельной обработки в Julia . Тогда я смог ускорить последовательную обработку с некоторой большой помощью, и теперь я пытаюсь получить дополнительную скорость от инструментов параллельной обработки в Юлии.

Для случая последовательной обработки я использовал 3-мерную матрицу (по сути, набор матриц одинакового размера, проиндексированных по 1-му измерению) и выполнял итерации по 1-му измерению. Я хотел дать pmap попытку, однако, более эффективно выполнять итерации по набору матриц.

Вот настройка кода. Чтобы использовать pmap с функцией v_iter, приведенной ниже, я преобразовал трехмерную матрицу в объект словаря с ключами словаря, равными значениям индекса в 1-м измерении (v_dict в приведенном ниже коде, с gcc равен размеру 1-го измерения). Функция v_iter принимает другие объекты словаря (E_opt_dict и gridpoint_m_dict ниже) в качестве дополнительных входных данных:

function v_iter(a,b,c)
   diff_v = 1
   while diff_v>convcrit
     diff_v = -Inf

     #These lines efficiently multiply the value function by the Markov transition matrix, using the A_mul_B function
     exp_v       = zeros(Float64,gkpc,1)
     A_mul_B!(exp_v,a[1:gkpc,:],Zprob[1,:])
     for j=2:gz
       temp=Array{Float64}(gkpc,1)
       A_mul_B!(temp,a[(j-1)*gkpc+1:(j-1)*gkpc+gkpc,:],Zprob[j,:])
       exp_v=hcat(exp_v,temp)
     end    

     #This tries to find the optimal value of v
     for h=1:gm
       for j=1:gz
         oldv = a[h,j]
         newv = (1-tau)*b[h,j]+beta*exp_v[c[h,j],j]
         a[h,j] = newv
         diff_v = max(diff_v, oldv-newv, newv-oldv)
       end
     end
   end
end

gz =  9  
gp =  13  
gk =  17  
gcc =  5  
gm    = gk * gp * gcc * gz
gkpc  = gk * gp * gcc
gkp = gk*gp
beta  = ((1+0.015)^(-1))
tau        = 0.35
Zprob = [0.43 0.38 0.15 0.03 0.00 0.00 0.00 0.00 0.00; 0.05 0.47 0.35 0.11 0.02 0.00 0.00 0.00 0.00; 0.01 0.10 0.50 0.30 0.08 0.01 0.00 0.00 0.00; 0.00 0.02 0.15 0.51 0.26 0.06 0.01  0.00 0.00; 0.00 0.00 0.03 0.21 0.52 0.21 0.03 0.00 0.00 ; 0.00 0.00  0.01  0.06 0.26 0.51 0.15 0.02 0.00 ; 0.00 0.00 0.00 0.01 0.08 0.30 0.50 0.10 0.01 ; 0.00 0.00 0.00 0.00 0.02 0.11 0.35 0.47 0.05; 0.00 0.00 0.00 0.00 0.00 0.03 0.15 0.38 0.43]
convcrit = 0.001   # chosen convergence criterion

E_opt                  = Array{Float64}(gcc,gm,gz)    
fill!(E_opt,10.0)

gridpoint_m   = Array{Int64}(gcc,gm,gz)
fill!(gridpoint_m,fld(gkp,2)) 

v_dict=Dict(i => zeros(Float64,gm,gz) for i=1:gcc)
E_opt_dict=Dict(i => E_opt[i,:,:] for i=1:gcc)
gridpoint_m_dict=Dict(i => gridpoint_m[i,:,:] for i=1:gcc) 

Для параллельной обработки я выполнил следующие две команды:

wp = CachingPool(workers())
addprocs(3)
pmap(wp,v_iter,values(v_dict),values(E_opt_dict),values(gridpoint_m_dict))

... который создал этот спектакль:

135.626417 seconds (3.29 G allocations: 57.152 GiB, 3.74% gc time)

Затем я попытался выполнить последовательную обработку:

for i=1:gcc
    v_iter(v_dict[i],E_opt_dict[i],gridpoint_m_dict[i])
end

... и получил лучшую производительность.

128.263852 seconds (3.29 G allocations: 57.101 GiB, 4.53% gc time)

Это также дает мне примерно ту же производительность, что и запуск v_iter на оригинальных трехмерных объектах:

v=zeros(Float64,gcc,gm,gz)
for i=1:gcc
    v_iter(v[i,:,:],E_opt[i,:,:],gridpoint_m[i,:,:])
end

Я знаю, что параллельная обработка требует времени установки, но когда я увеличиваю значение gcc, я все равно получаю примерно равное время обработки для последовательной и параллельной. Это кажется хорошим кандидатом для параллельной обработки, поскольку нет необходимости в обмене сообщениями между рабочими! Но я не могу заставить его работать эффективно.

Ответы [ 2 ]

0 голосов
/ 03 июля 2018

Основываясь на полезных советах @Przemyslaw Szufeul, я поместил ниже код, который правильно выполняет параллельную обработку. Запустив его один раз, я добился существенного улучшения во время выполнения: 77.728264 seconds (181.20 k allocations: 12.548 MiB)

В дополнение к переупорядочению команды wp и использованию рекомендованного Przemyslaw генератора я также преобразовал v_iter в качестве анонимной функции, чтобы избежать необходимости разбрасывать @everywhere вокруг кода для передачи функций и данных в рабочие.

Я также добавил return a к функции v_iter и установил v_a ниже равным выводу pmap, поскольку вы не можете передать по ссылке на удаленный объект.

addprocs(3)
v_iter = function(a,b,c)
   diff_v = 1
   while diff_v>convcrit
     diff_v = -Inf

     #These lines efficiently multiply the value function by the Markov transition matrix, using the A_mul_B function
     exp_v       = zeros(Float64,gkpc,1)
     A_mul_B!(exp_v,a[1:gkpc,:],Zprob[1,:])
     for j=2:gz
       temp=Array{Float64}(gkpc,1)
       A_mul_B!(temp,a[(j-1)*gkpc+1:(j-1)*gkpc+gkpc,:],Zprob[j,:])
       exp_v=hcat(exp_v,temp)
     end    

     #This tries to find the optimal value of v
     for h=1:gm
       for j=1:gz
         oldv = a[h,j]
         newv = (1-tau)*b[h,j]+beta*exp_v[c[h,j],j]
         a[h,j] = newv
         diff_v = max(diff_v, oldv-newv, newv-oldv)
       end
     end
   end
  return a
end

gz =  9  
gp =  13  
gk =  17  
gcc =  5  
gm    = gk * gp * gcc * gz
gkpc  = gk * gp * gcc
gkp   =gk*gp
beta  = ((1+0.015)^(-1))
tau        = 0.35
Zprob = [0.43 0.38 0.15 0.03 0.00 0.00 0.00 0.00 0.00; 0.05 0.47 0.35 0.11 0.02 0.00 0.00 0.00 0.00; 0.01 0.10 0.50 0.30 0.08 0.01 0.00 0.00 0.00; 0.00 0.02 0.15 0.51 0.26 0.06 0.01  0.00 0.00; 0.00 0.00 0.03 0.21 0.52 0.21 0.03 0.00 0.00 ; 0.00 0.00  0.01  0.06 0.26 0.51 0.15 0.02 0.00 ; 0.00 0.00 0.00 0.01 0.08 0.30 0.50 0.10 0.01 ; 0.00 0.00 0.00 0.00 0.02 0.11 0.35 0.47 0.05; 0.00 0.00 0.00 0.00 0.00 0.03 0.15 0.38 0.43]
convcrit = 0.001   # chosen convergence criterion

E_opt                  = Array{Float64}(gcc,gm,gz)    
fill!(E_opt,10.0)

gridpoint_m   = Array{Int64}(gcc,gm,gz)
fill!(gridpoint_m,fld(gkp,2)) 

v_a=[zeros(Float64,gm,gz) for i=1:gcc]
E_opt_a=[E_opt[i,:,:] for i=1:gcc]
gridpoint_m_a=[gridpoint_m[i,:,:] for i=1:gcc]

wp = CachingPool(workers())
v_a = pmap(wp,v_iter,v_a,E_opt_a,gridpoint_m_a)
0 голосов
/ 29 июня 2018

Вы создаете CachingPool перед добавлением рабочих процессов. Следовательно, ваш кеширующий пул, переданный pmap, говорит ему использовать только одного работника. Вы можете просто проверить это, запустив wp.workers, вы увидите что-то вроде Set([1]). Следовательно, это должно быть: addprocs(3) wp = CachingPool(workers()) Вы также можете использовать параметр командной строки Julia -p, например, julia -p 3 и затем вы можете пропустить команду addprocs(3).

Кроме того, ваши циклы for и pmap не эквивалентны. Объект Julia Dict является хэш-картой и, как и другие языки, не имеет ничего общего с порядком элементов. Следовательно, в вашем цикле for вы гарантированно получите один и тот же соответствующий i -й элемент, тогда как с values порядок значений не должен совпадать с исходным порядком (и вы можете иметь различный порядок для каждой из этих трех переменных в цикле pmap). Поскольку ключи для Dicts являются просто числами от 1 до gcc, вам следует просто использовать вместо них массивы. Вы можете использовать генераторы, очень похожие на Python. Для примера вместо v_dict=Dict(i => zeros(Float64,gm,gz) for i=1:gcc) использование v_dict_a = [zeros(Float64,gm,gz) for i=1:gcc]

Надеюсь, это поможет.

...