rapid - tag generator instagram
Tratando de encontrar una manera de construir un generador de Julia. (3)
Creo que la Task
ha sido sustituida por Channel()
. El uso en términos del generador de Fibonacci de Ben Lauwens es:
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
se puede usar usando
for a in fibonacci(10)
println(a)
end
1
1
2
3
5
8
13
21
34
55
Soy nueva en Julia. Principalmente programo en python.
En Python, si desea iterar sobre un gran conjunto de valores, es típico construir un llamado generador para ahorrar uso de memoria. Aquí hay un código de ejemplo:
def generator(N):
for i in range(N):
yield i
Me pregunto si hay algo parecido en Julia. Después de leer el manual de julia, la macro @task parece tener la misma (o similar) funcionalidad que el generador en python. Sin embargo, después de algunos experimentos, el uso de la memoria parece ser más grande que la matriz habitual en julia.
Utilizo @time
en IJulia para ver el uso de la memoria. Aquí está mi código de muestra:
[Actualización]: Agregar el código para el método generator
(El método generator
)
function generator(N::Int)
for i in 1:N
produce(i)
end
end
(versión del generador)
function fun_gener()
sum = 0
g = @task generator(100000)
for i in g
sum += i
end
sum
end
@time fun_gener()
tiempo transcurrido: 0.420731828 segundos (6507600 bytes asignados)
(Versión de matriz)
function fun_arry()
sum = 0
c = [1:100000]
for i in c
sum += i
end
sum
end
@time fun_arry()
tiempo transcurrido: 0.000629629 segundos (800144 bytes asignados)
¿Alguien podría decirme por qué @task
requerirá más espacio en este caso? Y si quiero guardar el uso de la memoria como un gran conjunto de valores, ¿qué puedo hacer?
Esta pregunta fue formulada (y contestada) hace bastante tiempo. Dado que esta pregunta ocupa un lugar destacado en las búsquedas de Google, me gustaría mencionar que tanto la pregunta como la respuesta están desactualizadas.
Hoy en día, sugeriría visitar https://github.com/BenLauwens/ResumableFunctions.jl para obtener una biblioteca de Julia con una macro que implemente generadores de rendimiento tipo 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
Dado que su alcance es mucho más limitado que las corrutinas completas, también es un orden de magnitud más eficiente que empujar valores en un canal, ya que puede compilar el generador en un FSM. (Los canales han reemplazado la antigua función de producir () mencionada en la pregunta y las respuestas anteriores).
Con eso dicho, seguiría sugiriendo ingresar a un canal como su primer enfoque si el rendimiento no es un problema, ya que las funciones de reanudación pueden ser complicadas a la hora de compilar su función y ocasionalmente pueden tener algún comportamiento en el peor de los casos. En particular, debido a que es una macro que se compila en un FSM en lugar de en una función, actualmente necesita anotar los tipos de todas las variables en la función de reanudación para obtener un buen rendimiento, a diferencia de las funciones de vainilla Julia donde JIT maneja esto cuando la función es primera llamada.
Recomiendo el blogpost de iteradores "engañados" de Carl Vogel, que analiza el protocolo de iterador de julia, las tareas y las co-rutinas con cierto detalle.
Véase también task-aka-coroutines en los documentos de julia.
En este caso, debe usar el tipo de rango (que define un protocolo de iterador ):
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
Más rápido y menos memoria asignada (como xrange
en python 2).
Un fragmento de la entrada del blog:
Desde https://github.com/JuliaLang/julia/blob/master/base/range.jl , aquí se describe cómo se define el protocolo de iterador de un rango:
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)
Observe que el siguiente método calcula el valor del iterador en el estado i. Esto es diferente de un iterador de Array, que simplemente lee el elemento a [i] de la memoria.
Los iteradores que explotan la evaluación retrasada de esta manera pueden tener importantes beneficios de rendimiento. Si queremos iterar sobre los enteros de 1 a 10,000, iterar sobre una matriz significa que tenemos que asignar unos 80 MB para mantenerla. Un rango solo requiere 16 bytes; el mismo tamaño que el rango de 1 a 100,000 o de 1 a 100,000,000.
Puedes escribir un método generador (usando Tareas):
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
Nota: si reemplaza el Rango con esto, el rendimiento es mucho peor (y asigna mucha más memoria):
julia> @time fun_arry()
elapsed time: 0.699122659 seconds (9 MB allocated)
5000050000