为什么此代码报告Clojure中的堆栈溢出

时间:2018-08-29 16:30:13

标签: clojure stack-overflow

这是重现Ross Ihaka作为不良R性能示例给出的一些代码的简单尝试。我对Clojure的持久数据结构是否可以提供任何改进感到好奇。 (https://www.stat.auckland.ac.nz/~ihaka/downloads/JSM-2010.pdf

但是,我什至还没有达到一垒,报道了堆栈溢出,还有很多其他事情要做。有任何想法吗?如果问题没有明显答案,我会提前道歉...

; Testing Ross Ihaka's example of poor R performance
; against Clojure, to see if persisntent data structures help

(def dd (repeat 60000 '(0 0 0 0)))

(defn repl-row [d i new-r]
   (concat (take (dec i) d) (list new-r) (drop i d)))

(defn changerows [d new-r]
  (loop [i 10000
         data d]
    (if (zero? i)
      data
      (let [j (rand-int 60000)
            newdata (repl-row data j new-r)]
        (recur (dec i) newdata)))))


user=> (changerows dd '(1 2 3 4))

StackOverflowError   clojure.lang.Numbers.isPos (Numbers.java:96)

此外,如果有人对如何在上面的示例中使用持久性功能数据结构发挥最大作用有任何想法,我将非常希望听到。使用不可变结构(上面的链接)报告的 not 加速约为500%!

1 个答案:

答案 0 :(得分:3)

查看StackOverflowError的堆栈跟踪,这似乎是一个“爆炸性的问题”(延迟/暂停计算)问题,与您的示例中的递归显然无关:

java.lang.StackOverflowError
    at clojure.lang.RT.seq(RT.java:528)
    at clojure.core$seq__5124.invokeStatic(core.clj:137)
    at clojure.core$concat$cat__5217$fn__5218.invoke(core.clj:726)
    at clojure.lang.LazySeq.sval(LazySeq.java:40)
    at clojure.lang.LazySeq.seq(LazySeq.java:49)
    at clojure.lang.RT.seq(RT.java:528)
    at clojure.core$seq__5124.invokeStatic(core.clj:137)
    at clojure.core$take$fn__5630.invoke(core.clj:2876)

更改此行以将newdata实现为矢量可解决此问题:

(recur (dec i) (vec newdata))

此解决方法是通过强制在每个步骤中实现concat的惰性序列来解决repl-rowconcat的使用。 concat返回惰性序列,并且在您的loop / recur中,您将先前concat个调用的惰性/未评估结果作为后续concat的输入调用会根据先前未实现的延迟序列返回更多延迟序列。直到循环完成,才会实现 final concat产生的惰性序列,由于它依赖于成千上万的{{​​1}}先前产生的惰性序列,导致堆栈溢出。

  

此外,如果有人对如何在上面的示例中使用持久性功能数据结构发挥最大优势有任何想法,我将非常希望听到。

由于似乎concat的用法是简单地替换集合中的元素,所以我们可以通过使用向量并将新项目concat-放入正确的位置来获得相同的效果向量的

assoc

请注意,这里不再有(def dd (vec (repeat 60000 '(0 0 0 0)))) (defn changerows [d new-r] (loop [i 10000 data d] (if (zero? i) data (let [j (rand-int 60000) newdata (assoc data j new-r)] (recur (dec i) newdata))))) 函数,我们只是使用索引和新值将repl-row插入assoc中。经过data的初步基准测试之后,这种方法似乎快了很多倍:

time

这是解决问题的另一种方法,将一系列替换视为无数个替换步骤序列,然后从该序列中采样:

"Elapsed time: 75836.412166 msecs" ;; original sample w/fixed overflow
"Elapsed time: 2.984481 msecs"     ;; using vector+assoc instead of concat