gal*_*dre 15 java clojure java-bytecode-asm
user=> (def r (range 1))
user=> (for [a r, b r, c r, d r, e r, f r, g r, h r :when (and (= 0 a) (not= 1 b))]
(list a b c d e f g h))
((0 0 0 0 0 0 0 0))
user=> (doseq [a r, b r, c r, d r, e r, f r, g r, h r :when (and (= 0 a) (not= 1 b))]
(println (list a b c d e f g h)))
CompilerException java.lang.RuntimeException: Method code too large!, compiling:(/tmp/form-init8346140986526777871.clj:1:1)
Run Code Online (Sandbox Code Playgroud)
这似乎来自clojure.asm.MethodWriter.我用谷歌搜索这个错误与Clojure几乎没有命中.
那么......到底是怎么回事?这个兔子洞有多深?这一行Clojure代码真的产生了> 65KB的方法(该值来自MethodWriter的源代码)?
如果这个答案正在讨论我遇到的问题,那么(a)为什么分块意味着它呈指数级而不是线性增长?(b)作为程序员,对我有什么影响?例如,这种行为是众所周知的吗?我是否应该避免使用doseq超过3或4个绑定的任何情况?这与使用for和doall?相比如何?
也许相关:
您所看到的是优化的一个令人讨厌的副作用,该优化被放入doseq宏的实现中以处理输入中的分块序列.你正确联系的问题的答案描述了潜在的原因,但没有充分说明事情发生的原因.
该doseq实现在内部使用一个函数,该函数以递归方式构建一系列嵌套loop构造,loop每个构造对应于每个级别的绑定doseq.在这个实现的一个天真的,未经优化的版本中,每个级别的循环只是运行它的主体,然后recur使用next其seq 的值调用.这些方面的东西:
(loop [s (seq input)]
(if s
(do (run-body (first s))
(recur (next s)))))
Run Code Online (Sandbox Code Playgroud)
但是,如果那个seq恰好是一个分块序列,那么这将导致不必要地创建许多中间seq对象,这些对象从不在循环体外使用.doseq已经进行的优化是if在loop一个分支内部处理分块序列,一个处理非分块序列.循环体在每个分支之间重复.如果循环体恰好是嵌套循环,那么您可以看到代码大小的指数增长是如何发生的 - 扩展代码的每个级别的循环都有两个子循环.
所以,为了回答你的问题,我不会确切地说代码大小的爆炸是有意的,但它是设计行为的结果doseq.它只是不是设计用于处理深层嵌套循环,而在野外我从未见过它使用超过一个或两个级别的绑定.
您可以复制的深层嵌套的语义doseq与组合for以及dorun(不要使用doall,因为这不必要地保留序列的头).这将允许您处理任何级别的嵌套,如果您恰好在紧密循环中运行分块序列,则会略微但可测量性能.
user> (time (doseq [x (range 10000) y (range 10000)] (* x y)))
"Elapsed time: 2933.543178 msecs"
user> (time (dorun (for [x (range 10000) y (range 10000)] (* x y))))
"Elapsed time: 5560.90003 msecs"
Run Code Online (Sandbox Code Playgroud)