2015-10-12 15 views
7

Mam następujący kod:Clojure - optymalizacja gwintowany mapa zmniejszyć

(defn series-sum 
    "Compute a series : (+ 1 1/4 1/7 1/10 1/13 1/16 ...)" 
    [n] 
    (->> (iterate (partial + 3) 1) 
     (map #(/ 1 %)) 
     (take n) 
     (reduce +) 
     float 
     (format "%.2f") 
     (str))) 

To działa dobrze, poza tym, że to działa w czasie kiedy eksploduje numery uzyskać duże. Na moim komputerze (series-sum 2500) jest może sekunda lub dwie, ale (series-sum 25000) i muszę zabić mojego REPL.

Próbowałem przenieść (take n) w miarę możliwości, ale to nie wystarczy. Czuję, że nie rozumiem czegoś o Clojure, ponieważ nie rozumiem, dlaczego byłoby wolniej (oczekiwałbym, że (series-sum 25000) potraktuje to mniej więcej 10 razy jako (series-sum 2500)).

Istnieje oczywiste rozwiązanie pętli/powtórzenia, aby zoptymalizować to, ale podoba mi się pomysł, aby móc drukować kroki i mieć jeden krok ((take n) wygląda jak docstring).

Jak mogę poprawić wydajność tego kodu przy zachowaniu możliwości debugowania?

Czy mogę jeszcze zmierzyć czas każdego kroku, aby zobaczyć, który zajmuje czas?

+2

Ważne: http://stackoverflow.com/q/26954404/251311 – zerkms

Odpowiedz

8

tak, jest to istotne dla linku @ zerkms. Mapować do wymiernych, prawdopodobnie powinny lepiej odwzorowane na pływaków:

(defn series-sum 
    "Compute a series : (+ 1 1/4 1/7 1/10 1/13 1/16 ...)" 
    [n] 
    (->> (iterate (partial + 3) 1) 
     (take n) 
     (map #(/ 1.0 %)) 
     (reduce +) 
     (format "%.2f"))) 

teraz działa znacznie szybciej:

user> (time (series-sum 2500000)) 
"Elapsed time: 686.233199 msecs" 
"5,95" 
+2

dla zabawy: wersja przetwornika: https://www.refheap.com/110572 – cfrick

+0

Wstyd: lubię być w stanie drukować sekwencja jako racjonalna ... dziękuję, że to naprawdę miłe. – nha

6

Dla tego typu operacji matematycznej, informatyki w pętli jest szybsze niż przy użyciu leniwe sekwencje. To jest o rząd wielkości szybciej niż inne odpowiedzi dla mnie:

(defn series-sum 
    [n] 
    (loop [i 0 
     acc 0.0] 
    (if (< i n) 
     (recur (inc i) 
      (+ acc (/ (float 1) (inc (* 3 i))))) 
     (format "%.2f" acc)))) 

Uwaga: nie potrzebujemy str ponieważ format Zwraca ciąg znaków.

Edycja: oczywiście nie jest to główny problem z kodem w pierwotnym pytaniu. Większa część poprawy pochodzi z eliminacji racjonalności, o czym świadczy inna odpowiedź. To tylko kolejna optymalizacja.

+1

Oczywiście pętla jest szybsza, ale czy jesteś pewien, że to główny problem? Spodziewałem się, że głównym problemem będzie rosnąca liczba racjonałów, co spowoduje, że każde mnożenie potrwa dłużej. Twoje rozwiązanie jest szybsze, ale przełączasz się na zmiennoprzecinkowe i używasz pętli, przez co trudno jest zorientować się, która jest główna poprawa wydajności. – amalloy

+1

Tak, miałem na myśli o rząd wielkości szybszy niż odpowiedź typu "przełącz się do pływania", która była już o około trzy rzędy wielkości szybsza niż racjonalność OP dla n w niskich tysiącach. –