Clojure thunks:堆栈溢出,带[0],但不带';(0)?
下面是一段代码,它给了我一个Clojure thunks:堆栈溢出,带[0],但不带';(0)?,clojure,stack-overflow,lazy-evaluation,thunk,Clojure,Stack Overflow,Lazy Evaluation,Thunk,下面是一段代码,它给了我一个StackOverflowError(从我的代码库中的一个实际示例中总结出来): (注意:此代码的设计目的只是演示问题或返回零。) 我可以通过以下任一步骤“拯救”它: 删除reduce行 将[0]更改为'(0) 在mapcat和计数之间的任意点添加(取100000000)(或任意整数) 我基本上对这种行为感到困惑(尤其是#2)。如果您有任何意见,我将不胜感激 (我有一种感觉,这可能与此有关,但我不太清楚是怎么回事——因此,如果它与此有关,我希望能解释一下原因。)我认为
StackOverflowError
(从我的代码库中的一个实际示例中总结出来):
(注意:此代码的设计目的只是演示问题或返回零。)
我可以通过以下任一步骤“拯救”它:
reduce
行[0]
更改为'(0)
mapcat
和计数之间的任意点添加(取100000000)
(或任意整数)
(我有一种感觉,这可能与此有关,但我不太清楚是怎么回事——因此,如果它与此有关,我希望能解释一下原因。)我认为问题在于
mapcat
,它调用concat
,使用cons
<矢量上的code>cons成本很高(可能会消耗堆栈),而列表的成本很低。这就是为什么从矢量更改为列表“修复”了问题。在正常情况下,reduce
使用循环
/重复
构造并使用恒定的堆栈空间。然而,您遇到了一个棘手的问题,这是由于减少了一个序列,该序列是通过提供concat
交替分块和非分块序列(向量[0]
是分块的;由生成的seq(取100(重复%)
是非分块的)而生成的
当concat
的第一个参数是分块序列时,它将返回一个惰性序列,该序列将使用chunk cons
生成另一个分块序列。否则,它将使用cons
生成非分块序列
同时,reduce
的实现使用了InternalReduce
协议(在clojure.core.protocols
中定义),该协议为结构提供了一个内部reduce
函数,与默认的第一次/下一次递归相比,该函数可以更有效地简化结构本身。分块序列的internal reduce
实现使用分块函数在循环中消费分块项,直到剩下一个非分块序列,然后调用剩余的internal reduce
。默认的internal reduce
实现类似地使用first
/next
消耗循环中的项目,直到基础seq类型发生更改,然后调用新seq类型上的internal reduce
,以分派到适当的优化版本。当您在concat
生成的seq中前进时,在分块子序列和非分块子序列之间交替进行,internal reduce
调用在堆栈上堆积起来,并最终将其破坏
这种情况的简单说明如下:
;; All chunked sub-seqs is OK
user> (reduce + (apply concat (take 10000 (repeat [1]))))
10000
;; All non-chunked sub-seqs is OK
user> (reduce + (apply concat (take 10000 (repeat '(1)))))
10000
;; Interleaved chunked and non-chunked sub-seqs blows the stack
user> (reduce + (apply concat (take 10000 (interleave (repeat [1]) (repeat '(1))))))
StackOverflowError clojure.lang.LazySeq.seq (LazySeq.java:60)
检查堆栈跟踪:
StackOverflowError
clojure.core/seq (core.clj:133)
clojure.core/interleave/fn--4525 (core.clj:3901)
clojure.lang.LazySeq.sval (LazySeq.java:42)
clojure.lang.LazySeq.seq (LazySeq.java:60)
clojure.lang.RT.seq (RT.java:484)
clojure.core/seq (core.clj:133)
clojure.core/take/fn--4232 (core.clj:2554)
clojure.lang.LazySeq.sval (LazySeq.java:42)
clojure.lang.LazySeq.seq (LazySeq.java:60)
clojure.lang.Cons.next (Cons.java:39)
clojure.lang.RT.next (RT.java:598)
clojure.core/next (core.clj:64)
clojure.core/concat/cat--3925/fn--3926 (core.clj:694)
clojure.lang.LazySeq.sval (LazySeq.java:42)
clojure.lang.LazySeq.seq (LazySeq.java:60)
clojure.lang.ChunkedCons.chunkedNext (ChunkedCons.java:59)
clojure.core/chunk-next (core.clj:660)
clojure.core.protocols/fn--6041 (protocols.clj:101)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6034 (protocols.clj:147)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6041 (protocols.clj:104)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6034 (protocols.clj:147)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6041 (protocols.clj:104)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6034 (protocols.clj:147)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6041 (protocols.clj:104)
至于你的解决办法:
- 显然,避免
可以完全防止问题的发生reduce
- 将
更改为[0]
将分块子序列替换为非分块子序列,从而避免了'(0)
中对分块序列的优化,并允许在具有恒定堆栈空间的单个循环中进行缩减内部reduce
- 插入
创建一个新的非分块序列,该序列完全由cons单元格组成take
count
只是在那里演示使用序列本身不会导致错误。在您的示例中,@Firth,如果愿意,您可以将(取3000)更改为(取3000000),并且仍然有效。您还可以将(take 3000)放在reduce步骤之后…reduce是严格的,并且使用恒定的堆栈空间-更有可能是concat/mapcat调用在强制之前未计算堆栈导致issue@noisesmith不过,这并不是一个真正的解释。当我删除reduce
时,count
为什么成功?为什么“(0)的行为会有所不同?为什么包装concat(或mapcat)不会产生(doall)帮助?您能否澄清为什么失败只发生在reduce
步骤中?如果我删除reduce
,为什么count
仍然有效(这会毫无困难地消耗整个序列)是的,就是这样。
StackOverflowError
clojure.core/seq (core.clj:133)
clojure.core/interleave/fn--4525 (core.clj:3901)
clojure.lang.LazySeq.sval (LazySeq.java:42)
clojure.lang.LazySeq.seq (LazySeq.java:60)
clojure.lang.RT.seq (RT.java:484)
clojure.core/seq (core.clj:133)
clojure.core/take/fn--4232 (core.clj:2554)
clojure.lang.LazySeq.sval (LazySeq.java:42)
clojure.lang.LazySeq.seq (LazySeq.java:60)
clojure.lang.Cons.next (Cons.java:39)
clojure.lang.RT.next (RT.java:598)
clojure.core/next (core.clj:64)
clojure.core/concat/cat--3925/fn--3926 (core.clj:694)
clojure.lang.LazySeq.sval (LazySeq.java:42)
clojure.lang.LazySeq.seq (LazySeq.java:60)
clojure.lang.ChunkedCons.chunkedNext (ChunkedCons.java:59)
clojure.core/chunk-next (core.clj:660)
clojure.core.protocols/fn--6041 (protocols.clj:101)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6034 (protocols.clj:147)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6041 (protocols.clj:104)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6034 (protocols.clj:147)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6041 (protocols.clj:104)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6034 (protocols.clj:147)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6041 (protocols.clj:104)