Пытаюсь найти способ построить Julia `generator`
Я новичок в Джулии.
Я в основном программирую на python.
В python,
если вы хотите итерации по большому набору значений,
типично построить так называемый генератор для экономии использования памяти.
Вот пример кода:
def generator(N):
for i in range(N):
yield i
Интересно, есть ли что-то подобное в Джулии.
После прочтения руководства julia,
Макрос @task, похоже, имеет ту же (или аналогичную) функциональность, что и генератор в python.
Однако,
после некоторых экспериментов,
использование памяти, по-видимому, больше, чем обычный массив в julia.
Я использую @time
в IJulia, чтобы увидеть использование памяти.
Вот мой пример кода:
[Обновить]: добавьте код для метода generator
(Метод generator
)
function generator(N::Int)
for i in 1:N
produce(i)
end
end
(версия генератора)
function fun_gener()
sum = 0
g = @task generator(100000)
for i in g
sum += i
end
sum
end
@time fun_gener()
прошедшее время: 0,420731828 секунд (выделено 6507600 байт)
(версия массива)
function fun_arry()
sum = 0
c = [1:100000]
for i in c
sum += i
end
sum
end
@time fun_arry()
прошедшее время: 0.000629629 секунд (выделено 800144 байт)
Может ли кто-нибудь сказать мне, почему @task
потребует больше места в этом случае?
И если я хочу сохранить использование памяти как дело с большим набором значений,
что я могу сделать?
Ответы
Ответ 1
Я рекомендую "обмануть итераторы" blogpost Карла Фогеля, в котором подробно обсуждается протокол итератора julia, задачи и совлокальные подпрограммы.
См. также task-aka-coroutines в документах julia.
В этом случае вы должны использовать тип Range (который определяет протокол итератора):
julia> function fun_arry()
sum = 0
c = 1:100000 # remove the brackets, makes this a Range
for i in c
sum += i
end
sum
end
fun_arry (generic function with 1 method)
julia> fun_arry() # warm up
5000050000
julia> @time fun_arry()
elapsed time: 8.965e-6 seconds (192 bytes allocated)
5000050000
Быстрее и меньше выделенной памяти (так же, как xrange
в python 2).
Отрывок из блога:
В https://github.com/JuliaLang/julia/blob/master/base/range.jl понимается, как определяется протокол итератора диапазонов:
start(r::Ranges) = 0
next{T}(r::Range{T}, i) = (oftype(T, r.start + i*step(r)), i+1)
next{T}(r::Range1{T}, i) = (oftype(T, r.start + i), i+1)
done(r::Ranges, i) = (length(r) <= i)
Обратите внимание, что следующий метод вычисляет значение итератора в состоянии i. Это отличается от Итератора массива, который просто считывает элемент a [i] из памяти.
Итераторы, которые используют задержанную оценку, как это, могут иметь важные преимущества в производительности. Если мы хотим итерации по целым числам от 1 до 10000, итерация по массиву означает, что мы должны выделить около 80 МБ для ее хранения. Для диапазона требуется только 16 байт; того же размера, что и диапазон от 1 до 100 000 или от 1 до 100 000 000.
Вы можете написать генераторный метод (используя Задачи):
julia> function generator(n)
for i in 1:n # Note: we're using a Range here!
produce(i)
end
end
generator (generic function with 2 methods)
julia> for x in Task(() -> generator(3))
println(x)
end
1
2
3
Примечание. Если вы замените Range на это, производительность намного хуже (и выделяет больше памяти):
julia> @time fun_arry()
elapsed time: 0.699122659 seconds (9 MB allocated)
5000050000
Ответ 2
Этот вопрос был задан (и ответил) довольно давно. Поскольку этот вопрос занимает высокое место в поисковых системах Google, я хотел бы упомянуть, что и вопрос, и ответ устарели.
В настоящее время я предлагаю проверить https://github.com/BenLauwens/ResumableFunctions.jl для библиотеки Julia с макросом, который реализует генераторы выходных данных типа Python.
using ResumableFunctions
@resumable function fibonnaci(n::Int) :: Int
a = 0
b = 1
for i in 1:n-1
@yield a
a, b = b, a+b
end
a
end
for fib in fibonnaci(10)
println(fib)
end
Поскольку его объем гораздо более ограничен, чем полные сопрограммы, он также на порядок эффективнее, чем нажатие значений в канал, поскольку он может скомпилировать генератор в FSM. (Каналы заменили функцию old product(), указанную в вопросе и предыдущих ответах).
С учетом сказанного я все же предлагаю входить в канал в качестве вашего первого подхода, если производительность не является проблемой, потому что возобновляемые функции иногда могут быть сложными при компиляции вашей функции и могут иногда поражать поведение в худшем случае.
Ответ 3
Я думаю, что Task
был заменен на Channel()
. Использование в терминах генератора Фибоначчи Ben Lauwens:
fibonacci(n) = Channel(ctype=Int) do c
a = 1
b = 1
for i in 1:n
push!(c, a)
a, b = b, a + b
end
end
его можно использовать с помощью
for a in fibonacci(10)
println(a)
end
1
1
2
3
5
8
13
21
34
55