gpt4 book ai didi

java - 对于与 Doseq(和方法代码太大)

转载 作者:太空狗 更新时间:2023-10-29 22:57:34 25 4
gpt4 key购买 nike

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)为什么分 block 意味着它呈指数增长而不是线性增长? (b) 作为程序员,这对我有什么影响?例如,这种行为是否众所周知且有意为之?我应该避免在超过 3 或 4 个绑定(bind)的任何情况下使用 doseq 吗?这与使用 fordoall 相比如何?

可能相关:

Clojure doseq generates huge code

Method code too large! exception using ASM

最佳答案

您所看到的是优化的令人讨厌的副作用,它被放入 doseq 宏的实现中以处理 chunked sequences在输入中。您正确链接的问题的答案描述了根本原因,但并没有充分说明为什么事情会这样发生。

doseq 实现在内部使用一个函数递归地构建一系列嵌套的loop 结构,一个loop 用于每个级别的绑定(bind)doseq。在这个实现的原始、未优化版本中,每个级别的循环将简单地运行其主体,然后使用其 seq 的 next 值调用 recur。沿着这些线的东西:

(loop [s (seq input)]
(if s
(do (run-body (first s))
(recur (next s)))))

但是,如果该 seq 恰好是一个分 block 序列,这将导致不必要地创建大量从未在循环体之外使用的中间 seq 对象。 doseq 所做的优化是在 loop 中放置一个 if,其中一个分支处理分 block 序列,一个分支处理非分 block 序列.循环体在每个分支之间重复。如果循环体恰好是一个嵌套循环,那么您可以看到代码大小呈指数增长是如何发生的——展开代码的每一层的循环都有两个子循环。

因此,为了回答您的问题,我不会确切地说代码大小的激增是有意为之,但这是 doseq 设计行为的结果。它只是不是为处理深层嵌套循环而设计的,而且在野外我从未见过它用于超过一个或两个级别的绑定(bind)。

您可以使用 fordorun 的组合重现深度嵌套的 doseq 的语义(不要使用 doall 因为这不必要地保留了 seq 的头部)。这将允许您处理任何级别的嵌套,如果您碰巧在一个紧密循环中运行一个分 block 序列,性能会受到轻微但可衡量的影响。

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"

关于java - 对于与 Doseq(和方法代码太大),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26353658/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com