Пытаюсь найти способ построить 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