试图找到一种构建 Julia `generator` 的方法

Trying to find a way to construct Julia `generator`

我是 Julia 的新手。 我主要在python.

编程

在python中, 如果你想遍历一大组值, 通常构造一个所谓的生成器来节省内存使用量。 这是一个示例代码:

def generator(N):
    for i in range(N):
        yield i

我想知道 Julia 是否有相似之处。 阅读朱莉娅手册后, @task 宏似乎与 python 中的生成器具有相同(或相似)的功能。 然而, 经过一些实验, 内存使用量似乎比 julia 中的普通数组大。

我在 IJulia 中使用 @time 来查看内存使用情况。 这是我的示例代码:

[更新]:添加 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 在这种情况下需要更多 space? 如果我想在处理大量值时节省内存使用量, 我能做什么?

我推荐 Carl Vogel 的 "tricked out iterators" 博文,其中详细讨论了 julia 的迭代器协议、任务和协程。

另请参阅 julia 文档中的 task-aka-coroutines


在这种情况下,您应该使用 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

更快,分配的内存更少(就像 python 2 中的 xrange)。

来自博文的片段:

From https://github.com/JuliaLang/julia/blob/master/base/range.jl, here’s how a Range’s iterator protocol is defined:

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)

Notice that the next method calculates the value of the iterator in state i. This is different from an Array iterator, which just reads the element a[i] from memory.

Iterators that exploit delayed evaluation like this can have important performance benefits. If we want to iterate over the integers 1 to 10,000, iterating over an Array means we have to allocate about 80MB to hold it. A Range only requires 16 bytes; the same size as the range 1 to 100,000 or 1 to 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

很久以前就有人问过(并回答过)这个问题。由于此问题在 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。 (频道已经替换了问题和之前答案中提到的旧 produce() 函数)。

话虽如此,如果性能不是问题,我仍然建议将进入通道作为您的第一种方法,因为可恢复函数在编译您的函数时有时会很挑剔,并且偶尔会遇到一些最坏情况的行为。特别是,因为它是一个编译为 FSM 而不是函数的宏,您目前需要注释 Resumable 函数中所有变量的类型以获得良好的性能,这与 vanilla Julia 函数不同,后者在函数运行时由 JIT 处理。第一次打电话。

我认为 Task 已被 Channel() 取代。 Ben Lauwens 的 Fibonacci 生成器的用法是:

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