Как я могу запустить простую операцию назначения параллельного массива в Джулии? - PullRequest
1 голос
/ 24 апреля 2020

Мне приходится многократно решать систему дифференциальных уравнений, итерируя по параметру. Для этого я запускаю al oop над списком параметров и сохраняю решение (оцениваемое по массиву значений времени) для каждого параметра. Таким образом, у меня есть 2D-массив, в котором я храню решения (каждая строка для значения параметра).

Теперь, поскольку любая итерация не имеет ничего общего с другой, я подумал сделать это параллельно.

Вот мой код:

using DifferentialEquations
using SharedArrays
using DelimitedFiles
using Distributed

function tf(x,w)
    return x*sin(w*x)
end

function sys!(dv,v,w,t)
    dv[1] = w*v[1]
    dv[2] = tf(v[1],w)
end

times = LinRange(0.1,2,25)

params = LinRange(0.1,1.2,100)

sols = SharedArray{Float64,2}((length(times),length(params)))

@distributed for i=1:length(params)
    println(i)
    init_val = [1.0,1.0]
    tspan = (0.0,2.0)
    prob = ODEProblem(sys!,init_val,tspan,params[i])
    sol = solve(prob)
    sols[:,i] .= sol(times)[2,:]
end

writedlm("output.txt",sols)

Теперь, когда я запускаю это без префикса @distributed для l oop, все работает отлично.

Однако, когда я запускаю этот код, оператор println не работает, и хотя файл «output.txt» хранится, он полон нулей.

Я запускаю этот код из командной строки следующим образом

julia -p 4 trycode.jl

Это не показывает вывод и просто работает в течение минуты и ничего не делает, хотя файл "output.txt" сохраняются. Это как если бы l oop никогда не вводится.

Буду очень признателен за помощь в настройке этой простой параллели l oop.

Ответы [ 2 ]

4 голосов
/ 24 апреля 2020

Как говорит Билл, в Julia есть два основных способа думать о параллелизме: многопоточная модель, которая была представлена ​​в Julia 1.3 и выполняет параллелизм совместно используемой памяти через макрос Threads.@threads, и распределенная обработка с использованием макроса Distributed.@distributed, который распараллеливает различные процессы Юлии.

Threads определенно ближе к ускорению параллелизма "automagi c" с минимальным перезаписыванием кода или вообще без него и часто отличным вариантом, хотя нужно позаботиться о том, чтобы какая бы операция ни выполнялась работа является потокобезопасной, поэтому всегда проверяйте, чтобы результаты были одинаковыми.

Поскольку ваш вопрос изначально касался @distributed параллелизма, позвольте мне также ответить на него. Если вы выполняете @distributed параллелизм, самая простая ментальная модель (я полагаю), чтобы подумать о том, что происходит, состоит в том, чтобы представить, что вы выполняете свой код в совершенно отдельных REPL Джулии.

Вот версия вашего кода, адаптированная для модели @distributed:

using Distributed
addprocs(2)

using SharedArrays
using DelimitedFiles

@everywhere begin 
    using DifferentialEquations

    tf(x,w) = x*sin(w*x)

    function sys!(dv,v,w,t)
        dv[1] = w*v[1]
        dv[2] = tf(v[1],w)
    end

    times = LinRange(0.1,2,25)
    params = LinRange(0.1,1.2,100)
end

sols = SharedArray{Float64,2}((length(times),length(params)))

@sync @distributed for i=1:length(params)
    println(i)
    init_val = [1.0,1.0]
    tspan = (0.0,2.0)
    prob = ODEProblem(sys!,init_val,tspan,params[i])
    sol = solve(prob)
    sols[:,i] .= sol(times)[2,:]
end

sols

Что изменилось?

  • Я добавил addprocs(2) в начале сценария. В этом нет необходимости, если вы запускаете Julia с p -2 (или с любым числом процессов, которое вы хотите), как вы это делаете, но мне часто бывает проще рассуждать о коде, когда он явно устанавливает параллельную среду в коде напрямую , Обратите внимание, что в настоящее время это невозможно для потоков, т. Е. Вам нужно установить переменную окружения JULIA_NUM_THREADS перед запуском Julia, и вы не можете изменить количество потоков после запуска и запуска.

  • Затем я переместил биты кода в блок @everywhere begin ... end. Это, по сути, запускает операции, включенные в блок, для всех процессов одновременно. Возвращаясь к ментальной модели запуска отдельных экземпляров Julia, вы должны посмотреть, что находится в вашем @distributed l oop, и убедиться, что все функции и переменные действительно определены во всех процессах. Так, например, чтобы каждый процесс знал, что такое ODEProblem, вам нужно сделать using DifferentialEquations для всех из них.

  • Наконец, я добавил @sync к распределенному l oop. На это ссылаются в документах для @distributed. Запуск макроса @distributed с for l oop порождает дескриптор асинхронного зеленого потока (Task) для распределенного выполнения и перемещается вперед на следующую строку. Поскольку вы хотите подождать до фактического завершения выполнения, требуется синхронизация @sync. Проблема с вашим исходным кодом заключается в том, что, не дожидаясь завершения зеленого потока (синхронизации), он будет проглатывать ошибки и просто сразу же возвращаться, поэтому ваш массив sol пуст. Это можно увидеть, если вы запустите исходный код и добавите только @sync - тогда вы получите TaskFailedException: on worker 2 - UndefVarError: #sys! not defined, который сообщит вам, что ваши рабочие процессы не знают о функциях, которые вы определили в главном процессе. На практике вам почти всегда нужно выполнение @sync, если только вы не планируете запускать много таких распределенных циклов параллельно. Вам также не нужно ключевое слово @sync, где вы используете агрегаторную функцию в распределенном l oop (@distributed (func) for i in 1:1000 форма l oop)

Теперь что такое лучшее решение здесь? Ответ - я не знаю. @threads является отличным вариантом для быстрого распараллеливания поточно-ориентированной работы без переписывания кода, и он все еще активно разрабатывается и совершенствуется, так что, вероятно, станет еще лучше в будущем. В стандартной библиотеке Distributed есть также pmap, который дает вам дополнительные опции, но этот ответ достаточно длинный! По моему личному опыту, ничто не заменит (1) размышления о вашей проблеме и (2) выполнение сравнительного анализа. Вы должны подумать о времени выполнения вашей проблемы (как общей, так и для каждой отдельной операции, которую вы хотите распространить) и требованиях к передаче сообщений / доступу к памяти.

Плюс в том, что, хотя вам, возможно, придется потратить немного усилий на размышления о вещах, у Джулии есть куча отличных вариантов, позволяющих максимально эффективно использовать любую аппаратную ситуацию на старом дрянном ноутбуке с двумя ядрами (например, тот, который я набираю это из) в многоузловые сверхвысокопроизводительные кластеры (что сделало Джулию одним из очень немногих языков программирования для достижения производительности в петафлопе - хотя, честно говоря, это немного сложнее, чем мой или Ответ Билла :))

3 голосов
/ 24 апреля 2020

Можете ли вы воспользоваться поточностью для вместо @distributed для? Это работает (Юлия 1.4):

using DifferentialEquations
using SharedArrays
using DelimitedFiles
using Distributed

function tf(x,w)
    return x*sin(w*x)
end

function sys!(dv,v,w,t)
    dv[1] = w*v[1]
    dv[2] = tf(v[1],w)
end

times = LinRange(0.1,2,25)

params = LinRange(0.1,1.2,100)

sols = SharedArray{Float64,2}((length(times),length(params)))

@Threads.threads for i=1:length(params)
    println(i)
    init_val = [1.0,1.0]
    tspan = (0.0,2.0)
    prob = ODEProblem(sys!,init_val,tspan,params[i])
    sol = solve(prob)
    sols[:,i] .= sol(times)[2,:]
end

writedlm("output.txt",sols)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...