2015-03-30 12 views
16

Jestem nowa dla Julii. Programuję głównie w Pythonie.Próba znalezienia sposobu na skonstruowanie Julii `generator`

W python, jeśli chcesz iterować na dużym zestawie wartości, , typowe jest skonstruowanie tak zwanego generatora, aby zaoszczędzić zużycie pamięci. Oto jeden przykład kodu:

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

Zastanawiam się, czy jest coś zarówno w Julii. Po przeczytaniu podręcznika julia, @task makro wydaje się mieć tę samą (lub podobną) funkcjonalność co generator w pythonie. Jednak po niektórych eksperymentach, użycie pamięci wydaje się być większe niż zwykle w julia.

Używam @time w IJulia, aby zobaczyć użycie pamięci. Oto mój przykładowy kod:

[Aktualizacja]: Dodaj kod generator metody
(Metoda generator)

function generator(N::Int) 
    for i in 1:N 
     produce(i) 
    end 
end 

(wersja generatora)

function fun_gener() 
    sum = 0 
    g = @task generator(100000) 
    for i in g 
     sum += i 
    end 
    sum 
end 

@time fun_gener()
upłynął czas: 0,420731828 sekund (przydzielono 6507600 bajtów)

(wersja array)

function fun_arry() 
    sum = 0 
    c = [1:100000] 
    for i in c 
     sum += i 
    end 
    sum 
end 

@time fun_arry()
upłynął czas: 0.000629629 sekund (800144 bajtów przydzielone)

Czy ktoś może mi powiedzieć, dlaczego @task będzie wymagać więcej miejsca w tym przypadku? A jeśli chcę zaoszczędzić zużycie pamięci jako zajmującej się dużym zestawem wartości, co mogę zrobić?

Odpowiedz

12

Polecam blog "tricked out iterators" autorstwa Carla Vogela, który omawia szczegółowo protokół iteratora Julii, zadania i rutynowe czynności.

Zobacz także task-aka-coroutines w julia docs.


W tym przypadku należy użyć typu Range (która definiuje protokół iterator):

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 

szybsze i mniej pamięci przydzielone (podobnie jak xrange w Pythonie 2).

urywek z blogpost:

Od https://github.com/JuliaLang/julia/blob/master/base/range.jl, oto jak protokół iterator zakresie jest zdefiniowana:

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) 

Zauważ, że następna metoda oblicza wartość iteratora w stanie ja . Różni się od iteratora Array, który odczytuje element a [i] z pamięci.

Iteratory, które wykorzystują opóźnioną ocenę w ten sposób, mogą przynieść istotne korzyści w zakresie wydajności. Jeśli chcemy iterować po liczbach całkowitych od 1 do 10 000, iteracja po tablicy oznacza, że ​​musimy przeznaczyć około 80 MB na jej utrzymanie. Zakres wymaga tylko 16 bajtów; taki sam rozmiar jak zakres od 1 do 100 000 lub od 1 do 100 000 000.


można napisać metodę generatora (za pomocą zadania):

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 

Uwaga: jeśli zastąpić zakresie z tym, wydajność jest znacznie uboższy (i przydziela sposób więcej pamięci):

julia> @time fun_arry() 
elapsed time: 0.699122659 seconds (9 MB allocated) 
5000050000 
+2

Nicea. Przepraszam, że zapomniałem opublikować mój kod dla metody generatora. Jest dokładnie taki sam jak twój. Dzięki za odpowiedź. – DboyLiao

+0

@DboyLiao nie ma problemu, myślę, że jest to przydatne pytanie, aby mieć tutaj! –

+1

Ale nadal jestem ciekawy, jak działa "@ task" i dlaczego zajmuje dużo więcej miejsca i jest jeszcze gorszy? Może to odkryję, kiedy zagłębię się w Julię. Z radością widzę, że Julia już wzięła pod uwagę problem z pamięcią i dostarcza nam ładny obiekt z zasięgiem. – DboyLiao

6

To pytanie zostało zadane (i udzielono odpowiedzi) jakiś czas temu. Ponieważ to pytanie jest wysoko oceniane pod kątem wyszukiwań w Google, chciałbym wspomnieć, że zarówno pytanie, jak i odpowiedź są nieaktualne.

Dzisiaj, proponuję sprawdzić https://github.com/BenLauwens/ResumableFunctions.jl dla biblioteki Julia z makrem, który implementuje generatory wydajności podobne do Pythona.

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 

Od jego zakres jest znacznie bardziej ograniczona niż pełnych współprogram, jest również o rząd wielkości bardziej efektywne niż pchanie wartości w kanale, ponieważ może skompilować generator w FSM. (Kanały zastąpiły poprzednią funkcję produktu() wymienioną w pytaniu i poprzednich odpowiedziach).

Powiedziałbym, że nadal sugerowałbym wejście na kanał jako pierwsze podejście, jeśli wydajność nie jest problemem, ponieważ funkcje, które można wznowić, mogą czasami być skomplikowane podczas kompilacji funkcji i czasami mogą być przyczyną najgorszego przypadku.

+0

Dzięki, @saolof. Doceniam twoją aktualizację:) – DboyLiao

2

Myślę, że Task został zastąpiony przez Channel(). Wykorzystanie w kategoriach generatora Fibonacciego Ben Lauwens jest:

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 

może być stosowany przy użyciu

for a in fibonacci(10) 
    println(a) 
end 
1                                                              
1                                                              
2                                                              
3                                                              
5                                                              
8                                                              
13                                                              
21                                                              
34                                                              
55 
+0

Zauważyłem, że 'julia> fibonacci (10) Kanał {Any} (sz_max: 0, sz_curr: 1)' Zastanawiam się, jak zdefiniować kanał typu 'Int' (zamiast' Any ") –

+0

Wygląda na to, że musisz użyć Kanał (ctype = Int) do ... koniec – davidav