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)
这似乎来自clojure.asm.MethodWriter。我用谷歌搜索这个错误与Clojure几乎没有命中。
那么......到底是怎么回事?这个兔子洞有多深?这一行Clojure代码是否真的产生了一个> 65KB的方法(该值来自MethodWriter的源代码)?
如果this answer正在遇到我遇到的问题,那么(a)为什么分块意味着它呈指数增长而不是线性增长? (b)作为程序员,对我有什么影响?例如,这种行为是众所周知的吗?对于超过3或4个绑定的任何情况,我应该避免使用doseq
吗?与使用for
和doall
相比,这相比如何?
也许相关:
答案 0 :(得分:8)
您所看到的是优化的一个令人讨厌的副作用,该优化被放入doseq
宏的实现中以处理输入中的chunked sequences。你正确联系的问题的答案描述了根本原因,但并没有充分阐明事情发生的原因。
doseq
实现在内部使用一个函数,递归地构建一系列嵌套loop
构造,loop
中每个绑定级别一个doseq
。在这个实现的一个天真的,未优化的版本中,每个级别的循环只是运行它的主体,然后使用recur
的seq调用next
。这些方面的东西:
(loop [s (seq input)]
(if s
(do (run-body (first s))
(recur (next s)))))
如果seq恰好是一个分块序列,那么这将导致不必要地创建许多中间seq对象,这些对象从未在循环体外使用。 doseq
所做的优化是将if
置于loop
内,其中一个分支处理分块序列,另一个处理非分块序列。循环体在每个分支之间重复。如果循环体恰好是嵌套循环,那么你可以看到代码大小的指数增长是如何发生的 - 扩展代码的每个级别的循环都有两个子循环。
所以,为了回答你的问题,我不能确切地说代码大小的爆炸是有意的,但它是doseq
设计行为的结果。它只是设计用于处理深度嵌套的循环,而且在野外我从未见过它使用超过一个或两个级别的绑定。
您可以使用doseq
和for
的组合重现深层嵌套dorun
的语义(不要使用doall
,因为这会不必要地保留头部(seq)。这将允许您处理任何级别的嵌套,如果您恰好在紧密循环中运行分块序列,则会有轻微但可测量的性能损失。
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"
答案 1 :(得分:1)
当我使用java创建自己的编译器时,我遇到了类似的问题。
我宣布了一个非常大的矩阵。 对我来说,溶液被分成小矩阵。 这只是一个建议,也许你可以做类似的事情,例如:
(def r (range 1))
(defn foo [a b c d]
(doseq [e r, f r, g r, h r] (println "Hi")))
(doseq [a r, b r, c r, d r :when (and (= 0 a) (not= 1 b))]
(foo a b c d))