懒惰序列中“失去你的头”的解释

时间:2011-04-18 03:08:57

标签: functional-programming clojure lazy-evaluation jvm-languages

在Clojure编程语言中,为什么这段代码会以漂亮的颜色传递?

(let [r (range 1e9)] [(first r) (last r)])

虽然这个失败了:

(let [r (range 1e9)] [(last r) (first r)])

我知道这是关于“失去理智”的建议,但请你向我解释一下吗?我还没能消化它。

更新:
选择正确的答案真的很难,两个答案的内容非常丰富 注意:代码片段来自“Clojure的喜悦”。

4 个答案:

答案 0 :(得分:29)

要详细说明dfanRafał的答案,我已花时间使用YourKit个分析器运行这两个表达式。

看到JVM正常运行真是令人着迷。第一个程序是GC友好的,JVM非常注重管理它的内存。

我画了一些图表。

GC友好:(让[r(范围1e9)] [(第一个r)(最后一个r)])

enter image description here

这个程序的内存运行很低;总体而言,不到6兆字节。如前所述,它非常友好GC,它会产生很多集合,但是使用的CPU很少。

头部持有者:(让[r(范围1e9)] [(最后一个r)(第一个r)])

enter image description here

这个人非常渴望记忆。它高达300 MB的RAM,但这还不够,程序没有完成(JVM死不到一分钟)。 GC占用CPU时间的90%,这表明它拼命想要释放任何内存,但却找不到任何内存(收集的对象几乎没有)。

编辑第二个程序内存不足,触发堆转储。对此转储的分析表明,70%的内存是java.lang.Integer对象,无法收集。这是另一个截图:

enter image description here

答案 1 :(得分:25)

range根据需要生成元素。

(let [r (range 1e9)] [(first r) (last r)])的情况下,它抓取第一个元素(0),然后生成十亿个 - 2个元素,随着它们抛出它们,然后抓取最后一个元素(999,999,999)。它永远不需要保留序列的任何部分。

(let [r (range 1e9)] [(last r) (first r)])的情况下,它生成十亿个元素以便能够评估(last r),但它还必须保持它生成的列表的开头以便以后评估(first r)。所以它不能随意抛出任何东西,并且(我猜)会耗尽内存。

答案 2 :(得分:11)

这里真正关注的是序列与r的绑定(不是已经评估过的(first r),因为你不能从它的值中评估整个序列。)

在第一种情况下,当(last r)被评估时,绑定不再存在,因为没有更多的表达式需要r进行评估。在第二种情况下,尚未评估的(first r)的存在意味着评估者需要保持对r的绑定。

要显示差异,请评估确定:

user> (let [r (range 1e8) a 7] [(last r) ((constantly 5) a)])

[99999999 5]

虽然失败了:

(let [r (range 1e8) a 7] [(last r) ((constantly 5) r)])

即使(last r)后面的表达式忽略r,评估者也不会 智能,并保持绑定到r,从而保持整个序列。

编辑:我发现了一篇帖子,其中Rich Hickey解释了在上述情况下负责清除对头部引用的机制的详细信息。这是:Rich Hickey on locals clearing

答案 3 :(得分:2)

有关技术说明,请转到http://clojure.org/lazyDon't hang (onto) your head

部分提到了建议