Нужно оптимизировать мой код Clojure, который занимает слишком много времени - PullRequest
0 голосов
/ 04 июля 2019

У меня есть файл журнала размером 1,6 ГБ, содержащий 2 миллиона записей.Я читаю содержимое журнала в канал, выполняю некоторые преобразования и записываю содержимое обратно в другой канал.

Наконец, я записываю содержимое второго канала в файл.

Мой код работает нормально, и результаты ожидаются.Однако вся операция занимает ~ 45 секунд, что слишком долго.

Мне нужно сократить время.

(def reader-channel (delay (let [temp (chan)]
                         (go
                           (with-open [reader (clojure.java.io/reader "My_Big_Log")]
                             (doseq [ln (line-seq reader)]
                               (>! temp ln)))
                           (close! temp))
                         temp)))



(def writer-channel (chan))

(defn make-collection [] (loop [my-coll []] (let [item (<!! @reader-channel)]
  (if (nil? item)
    my-coll
    (do (let [temp (re-find #"[a-z]+\.[a-z]+\.[a-z]+" item)]
          (recur (conj my-coll temp))))))))

(def transformed-collection (delay (partition-by identity
                                             (remove nil? (sort (make-collection))))))

(defn transform [] (go-loop [counter 0]
(if (>= counter (count @transformed-collection))
  (do (close! writer-channel)
      (println "Goodbye"))
  (do (let [item (str "Referrer " (+ counter 1) ": "
                      (first (nth @transformed-collection counter)))]
        (>! writer-channel item))
      (let [item (str "Number of entries associated with this referrer: "
                      (count (nth @transformed-collection counter)))]
        (>! writer-channel item))
    (recur (inc counter))))))

(defn write-to-file [] (with-open [wrtr (clojure.java.io/writer "Result.txt" :append true)]
(loop []
  (when-let [temp (<!! writer-channel)]
    (.write wrtr (str temp "\n"))
    (recur)))))

Я прошу прощения за плохие отступы и форматирование.

Ответы [ 3 ]

1 голос
/ 04 июля 2019

Я не люблю кодировать гольф, но кажется, что это довольно просто. Мы хотим посчитать частоту реферера, поэтому давайте просто сделаем это:

  (with-open [reader (clojure.java.io/reader "My_Big_Log")]
    (frequencies
     (map #(re-find #"[a-z]+\.[a-z]+\.[a-z]+")
          (line-seq reader))))

Подсчет рефереров путем генерации списка из всех 2 миллионов из них, а затем сортировки и разбиения означает, что вы переносите большое количество ненужных данных. Это делает это в сложности пространства O (источники), а не O (строки), что в зависимости от ваших журналов может быть огромным сокращением.

Мне также не ясно, почему вы используете core.async. Это очень мало прибавит к этому простому счету и очень затруднит понимание того, что происходит в коде.

Наконец - просто профиль. Он покажет вам много интересного о вашем коде, которого вы, возможно, не знали.

1 голос
/ 04 июля 2019

transform выполняет несколько чрезвычайно дорогих операций каждый раз в цикле.count и nth в ленивой последовательности каждый занимает O (n) времени.Вместо того, чтобы использовать любой из них, обработайте последовательность лениво с помощью first и next.

0 голосов
/ 05 июля 2019

sort на 2М записи медленные.Плюс count и nth также дороги в ленивой последовательности.Вы можете избежать их (вместе со всеми промежуточными последовательностями) с помощью преобразователя.На моем MBP записи 2M заняли ~ 5 секунд.

(defn transform [input-f output-f]
  (let [read-ch  (chan 1 (comp (map (partial re-find #"[a-z]+\.[a-z]+\.[a-z]+"))
                               ;; remove other lines
                               (remove nil?)
                               ;; transducer bag is like a set but with counter. e.g. {"a.b.c" 1  "c.d.e" 3}
                               (bag)
                               ;; make each map entry as a sequence element (["a.b.c" 1] ["c.d.e" 3])
                               cat
                               ;; generate output lines
                               (map-indexed (fn [i [x cnt]]
                                              [(str "Referrer " i ": " x)
                                               (str "Number of entries associated with this referrer: " cnt)]))
                               ;; flatten the output lines  (["l1" "l2"] ["l3" "l4"]) => ("l1" "l2" "l3" "l4")
                               cat))
        write-ch (chan)]

    ;; wire up read-ch to write-ch
    (pipe read-ch write-ch true)

    ;; spin up a thread to read all lines into read-ch
    (thread
      (with-open [reader (io/reader input-f)]
        (<!! (onto-chan read-ch (line-seq reader) true))))

    ;; write the counted lines to output
    (with-open [wtr (io/writer output-f)]
      (loop []
        (when-let [temp (<!! write-ch)]
          (.write wtr (str temp "\n"))
          (recur))))))

(time
 (transform "input.txt" "output.txt"))
;; => "Elapsed time: 5286.222668 msecs"

А вот подсчетная сумка 'one-off', которую я использовал:

(defn bag []
  (fn [rf]
    (let [state (volatile! nil)]
      (fn
        ([] (rf))
        ([result] (if @state
                    (try
                      (rf result @state)
                      (finally
                        (vreset! state nil)))
                    (rf result)))
        ([result input]
         (vswap! state update input (fnil inc 0))
         result)))))

Вот пример вывода:

Referrer 0: h.i.j
Number of entries associated with this referrer: 399065
Referrer 1: k.l.m
Number of entries associated with this referrer: 400809
Referrer 2: a.b.c
Number of entries associated with this referrer: 400186
Referrer 3: c.d.e
Number of entries associated with this referrer: 399667
Referrer 4: m.n.o
Number of entries associated with this referrer: 400273
...