Clojure惯用和内存有效的循环

时间:2015-09-22 06:30:06

标签: memory clojure lisp idiomatic

我正在Clojure中实现一个简单的算法,即使在我的:jvm-opts ["-Xmx4G"]上设置了project.clj,它也会一直在摧毁内存。假设以下数据结构:

(def inf Double/POSITIVE_INFINITY)
(def min-dist (atom {:1 {:1 0 :2 4} :2 {:1 4 :2 0 :3 5} :3 {:2 5 :3 0}}))
(def vertexes [:1 :2 :3])

以下内容将在较大的输入(|vertexes| = 100)上耗尽:

(for [k vertexes i vertexes j vertexes]
  (do
    (println " " i " " j " "k)
    (let [s (+ (get-in @min-dist [i k] inf) (get-in @min-dist [k j] inf))]
      (if (> (get-in @min-dist [i j] inf) s) (swap! min-dist assoc-in [i j] s)))))

输出:

OutOfMemoryError Java heap space  java.util.Arrays.copyOf (Arrays.java:2367)

我很确定这是一个reduce选项,可以让所有东西干净而快速,但我找不到它。看起来swap!占用了大量的内存空间,我是对的吗?

两个奖金问题:

  1. 如果我删除println行(当然还有do),代码将快速运行,但min-dist将不会更新,就好像循环不是执行。为什么?

  2. 使用lein run运行时会看到相同的行为,即使其中有println。为什么呢?

  3. 对新Clojurist的任何帮助将不胜感激。 =)

1 个答案:

答案 0 :(得分:9)

你的子问题#1是关键。

for生成 lazy 列表,因此除非实际读取结果,否则不会执行任何工作。如果你想要对结果进行评估,你可以将整个事件包装在dorun的调用中,该调用遍历列表而不会将整个内容保留在内存中。我将这种情况称为“被懒惰的小虫咬伤”,大多数Clojurians发生的事情比他们更容易发生; - )

user> @min-dist
{:1 {:1 0, :2 4}, :2 {:1 4, :2 0, :3 5}, :3 {:2 5, :3 0}}
user> (time
        (dorun (for [k vertexes i vertexes j vertexes]
                 (let [s (+ (get-in @min-dist [i k] inf) (get-in @min-dist [k j] inf))]
                  (if (> (get-in @min-dist [i j] inf) s) (swap! min-dist assoc-in [i j] s))))))
"Elapsed time: 4.272336 msecs"
nil
user> @min-dist
{:1 {:1 0, :2 4, :3 9}, :2 {:1 4, :2 0, :3 5}, :3 {:2 5, :3 0, :1 9}}

删除println是一个好主意,因为有一个100个顶点用于表达的列表(它不是在其他语言中有词的意义上的循环)将运行100万次(100 * 100 * 100),因此将其打印出来需要一段时间。

因为我是奖励积分的傻瓜:这里是使用reduce:

user> (def min-dist {:1 {:1 0 :2 4} :2 {:1 4 :2 0 :3 5} :3 {:2 5 :3 0}})
#'user/min-dist
user> (time
       (->> (for [k vertexes i vertexes j vertexes]     ;; start by making a sequence of all the combinatioins of indexes
              [i j k])
            (reduce
             (fn [result [i j k]]                       ;; the reducer function takes the result so far and either modifies it 
               (let [s (+ (get-in result [i k] inf)     ;; or passes it through unchanged.
                          (get-in result [k j] inf))]
                 (if (> (get-in result [i j] inf) s)    ;; depending on this if expression here.
                   (assoc-in result [i j] s)            ;; pass on the changed value
                   result)))                            ;; pass on the original value, unchanged
             min-dist)))                                ;; this argument is the initial value.
                                                        ;; the ->> expression places the result of the for expression here. (as the last argument)
"Elapsed time: 5.099 msecs"
{:1 {:1 0, :2 4, :3 9}, :2 {:1 4, :2 0, :3 5}, :3 {:2 5, :3 0, :1 9}}

这使得min-dist中的原始值保持不变。