Юлия - статус функции карты

Есть ли удобный способ каким-то образом получить «статус» карты / pmap в Julia?

Если бы у меня был массив a = [1:10], я бы хотел:

1: перечислить массив и использовать условное if для добавления команды печати

((index,value) -> 5*value ......, enumerate(a)

и там, где есть ".......", можно было бы "связать" анонимную функцию с чем-то вроде

"5*value and then print index/length(a) if index%200 == 0"

2: знаете, существует ли уже существующая опция для этого, поскольку pmap предназначен для параллельных задач, которые обычно используются для больших процессов, поэтому было бы разумно, чтобы это уже существовало?

Кроме того, есть ли способ заставить анонимные функции выполнять две «отдельные» вещи одну за другой?

Пример

если бы у меня был

a = [1:1000]
function f(n) #something that takes a huge ammount of time
end 

и я выполняю

map(x -> f(x), a)

REPL распечатает статус

"0.1 completed"
.
.
.
"0.9 completed"

Решение

Ответ Криса Ракаукаса

Немного странно, что пакет ProgressMeter не включает это по умолчанию

Pkg.add("ProgressMeter")
Pkg.clone("https://github.com/slundberg/PmapProgressMeter.jl")
@everywhere using ProgressMeter
@everywhere using PmapProgressmeter
pmap(x->begin sleep(1); x end, Progress(10), 1:10)

PmapProgressMeter на github


person isebarn    schedule 04.08.2016    source источник
comment
Непонятно, что вы имеете в виду под статусом. Приведите пример вывода, который вы хотите получить для конкретного входного вектора.   -  person David P. Sanders    schedule 04.08.2016
comment
'статус' как то, как далеко через оценку, я опубликовал пример, чтобы прояснить   -  person isebarn    schedule 04.08.2016


Ответы (4)


ProgressMeter.jl имеет ветку для pmap.

Вы также можете заставить работать индикатор выполнения Juno внутри pmap. Это своего рода использование недокументированных вещей, поэтому вам следует спросить в Gitter, если вам нужна дополнительная информация, потому что публикация этого public просто запутает людей, если / когда он изменится.

person Chris Rackauckas    schedule 04.08.2016
comment
Хорошая уловка вероятного вопроса XY Problem: p - person Tasos Papastylianou; 04.08.2016
comment
Отлично! Спасибо. - person isebarn; 04.08.2016

Вы можете создать функцию с «состоянием» по вашему запросу, реализовав «закрытие». Например.

julia> F = function ()
  ClosedVar = 5
  return (x) -> x + ClosedVar
end;
julia> f = F();
julia> f(5)
10
julia> ClosedVar = 1000;
julia> f(5)
10

Как видите, функция f поддерживает «состояние» (т. Е. Внутренняя переменная ClosedVar является локальной для F, а f поддерживает доступ к ней, хотя сам F технически давно вышел за рамки.

Обратите внимание на разницу с обычным, незамкнутым определением функции:

julia> MyVar = 5;
julia> g(x) = 5 + MyVar;
julia> g(5)
10
julia> MyVar = 1000;
julia> g(5)
1005

Вы можете создать собственное закрытие, которое запрашивает / обновляет свои закрытые переменные при запуске и каждый раз делает что-то другое в зависимости от своего состояния.

Сказав это, из вашего примера вы, кажется, ожидаете, что pmap будет работать последовательно. Это не гарантируется. Поэтому не полагайтесь на подход «какой индекс является этой обработкой потока» для печати каждых 200 операций. Вам, вероятно, придется поддерживать закрытую переменную «счетчик» внутри вашего закрытия и полагаться на нее. Что, по-видимому, также подразумевает, что ваше закрытие должно быть доступно @everywhere

person Tasos Papastylianou    schedule 04.08.2016

Почему бы просто не включить его в определение вашей функции, чтобы распечатать эту информацию? Например.

function f(n) #something that takes a huge amount of time
    ...
    do stuff.
    ...
    println("completed $n")
end 

И вы можете добавить дополнительный аргумент к своей функции, если хотите, который будет содержать эти 0.1, ..., 0.9 в вашем примере (что я не совсем уверен, что это такое, но какими бы они ни были, они могут быть просто аргумент в вашей функции).

Если вы посмотрите на приведенный ниже пример для pmap и @parallel, вы найдете пример функции, переданной в pmap, которая печатает вывод.

См. Также это и это сообщение SO с информацией о передаче нескольких аргументов функциям, используемым с map и pmap.



В документации Джулии говорится, что

pmap () разработан для случая, когда каждый вызов функции выполняет большой объем работы. Напротив, @parallel for может обрабатывать ситуации, когда каждая итерация крошечная, возможно, просто суммируя два числа.

На это есть несколько причин. Во-первых, pmap влечет за собой более высокие начальные затраты на создание рабочих мест для рабочих. Таким образом, если рабочих мест очень мало, эти начальные затраты могут стать неэффективными. Однако, наоборот, pmap выполняет «более разумную» работу по распределению рабочих мест между рабочими. В частности, он создает очередь заданий и отправляет новое задание каждому рабочему, когда этот рабочий становится доступным. @parallel, напротив, распределяет всю работу, которая должна быть сделана среди рабочих, когда она вызывается. Таким образом, если одни работники выполняют свою работу дольше, чем другие, вы можете столкнуться с ситуацией, когда большинство ваших работников закончили работу и бездействуют, в то время как некоторые остаются активными чрезмерное количество времени, завершая свою работу. Однако такая ситуация менее вероятна для очень небольших и простых работ.

Это иллюстрирует следующее: предположим, что у нас есть два рабочих, один из которых медленный, а другой в два раза быстрее. В идеале мы хотели бы дать быстрому исполнителю в два раза больше работы, чем медленному. (или у нас могут быть быстрые и медленные работы, но принцип тот же самый). pmap выполнит это, а @parallel - нет.

Для каждого теста мы инициализируем следующее:

addprocs(2)

@everywhere begin
    function parallel_func(idx)
        workernum = myid() - 1 
        sleep(workernum)
        println("job $idx")
    end
end

Теперь для теста @parallel мы запускаем следующее:

@parallel for idx = 1:12
    parallel_func(idx)
end

И вернемся к распечатке:

julia>     From worker 2:    job 1
    From worker 3:    job 7
    From worker 2:    job 2
    From worker 2:    job 3
    From worker 3:    job 8
    From worker 2:    job 4
    From worker 2:    job 5
    From worker 3:    job 9
    From worker 2:    job 6
    From worker 3:    job 10
    From worker 3:    job 11
    From worker 3:    job 12

Это почти сладко. Рабочие «разделили» работу поровну. Обратите внимание, что каждый рабочий выполнил 6 заданий, хотя рабочий 2 в два раза быстрее, чем рабочий 3. Это может быть трогательно, но неэффективно.

Для теста pmap я выполняю следующее:

pmap(parallel_func, 1:12)

и получите результат:

From worker 2:    job 1
From worker 3:    job 2
From worker 2:    job 3
From worker 2:    job 5
From worker 3:    job 4
From worker 2:    job 6
From worker 2:    job 8
From worker 3:    job 7
From worker 2:    job 9
From worker 2:    job 11
From worker 3:    job 10
From worker 2:    job 12

Теперь обратите внимание, что рабочий 2 выполнил 8 работ, а рабочий 3 выполнил 4. Это точно пропорционально их скорости и тому, что мы хотим для оптимальной эффективности. pmap - мастер сложных задач - каждый по способностям.

person Michael Ohlrogge    schedule 04.08.2016
comment
Вы правы, это хороший способ решить эту проблему. Я ранее читал (неправильно), что pmap может принимать только функцию и один вектор в качестве аргумента, что оказалось неверным, поэтому проблема более или менее решается таким образом, предоставляя дополнительный вектор «подсчета» для печати из - person isebarn; 04.08.2016
comment
эта функция не очень хороша в качестве аргумента для pmap ... вероятно, лучше всего передать закрытие, которое автоматически обновляет свое состояние при вызове. - person Tasos Papastylianou; 04.08.2016
comment
Я полагаю, вы могли бы сделать это функцией с двумя аргументами, а также передать массив индексов для сопоставления, чтобы отслеживать обрабатываемые элементы ... но, опять же, это будет ненадежно для pmap, поскольку не гарантируется, что он будет последовательным . - person Tasos Papastylianou; 04.08.2016
comment
pmap (в отличие от @parallal) будет передавать аргументы последовательно, просто завершение не обязательно будет последовательным. - person Michael Ohlrogge; 04.08.2016

Еще одна возможность - использовать SharedArray в качестве счетчика, совместно используемого рабочими. Например.

addprocs(2)

Counter = convert(SharedArray, zeros(Int64, nworkers()))

## Make sure each worker has the SharedArray declared on it, so that it need not be fed as an explicit argument
function sendto(p::Int; args...)
  for (nm, val) in args
    @spawnat(p, eval(Main, Expr(:(=), nm, val)))
  end
end

for (idx, pid) in enumerate(workers())
  sendto(pid, Counter = Counter)
end
@everywhere global Counter


@everywhere begin
    function do_stuff(n)
        sleep(rand())
        Counter[(myid()-1)] += 1
        TotalJobs = sum(Counter)
        println("Jobs Completed = $TotalJobs")
    end
end

pmap(do_stuff, 1:10)
person Michael Ohlrogge    schedule 04.08.2016
comment
Я на самом деле пробовал то же самое, но это показалось немного сложным, и мне нужно было более простое решение. Однако мне никогда не удавалось получить рабочих, которые я назначил типам SharedArray, чтобы иметь доступ к массиву, теперь я вижу, что их нужно объявить глобальными. Параллелизм в Юлии - это сложно - person isebarn; 04.08.2016
comment
@isebarn Да, к параллелизму немного сложно привыкнуть. Я работаю над созданием документации SO по этому поводу, но есть еще кое-что, чего мне еще не удалось. На самом деле глобальное объявление здесь не является обязательным - это операция sendto. Global просто делает аргументы глобальными для каждого рабочего, чтобы функции было немного проще и быстрее узнать, где их искать, но в конечном итоге она найдет их даже без этого. - person Michael Ohlrogge; 04.08.2016