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

Chi*_*ron 19 functional-programming clojure jvm-languages lazy-evaluation

在Clojure编程语言中,为什么这段代码会以绚丽的色彩传递?

(let [r (range 1e9)] [(first r) (last r)])
Run Code Online (Sandbox Code Playgroud)

虽然这个失败了:

(let [r (range 1e9)] [(last r) (first r)])
Run Code Online (Sandbox Code Playgroud)

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

更新:
真的很难找到正确的答案,两个答案是惊人的信息.
注意:代码片段来自"Clojure的喜悦".

Leo*_*nel 29

为了详细说明dfanRafał的答案,我花时间用YourKit分析器运行这两个表达式.

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

我画了一些图表.

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

在此输入图像描述

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

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

在此输入图像描述

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

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

在此输入图像描述

  • 丑陋与否,它们非常有用.非常好的帖子. (4认同)

dfa*_*fan 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).所以它不能随意抛出任何东西,并且(我认为)内存不足.


Raf*_*ird 11

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

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

为了显示差异,这评估OK:

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

[99999999 5]
Run Code Online (Sandbox Code Playgroud)

虽然失败了:

(let [r (range 1e8) a 7] [(last r) ((constantly 5) r)])
Run Code Online (Sandbox Code Playgroud)

即使后面的表达式(last r)忽略了r,评估器也不是那么聪明并保持绑定r,从而保持整个序列.

编辑:我发现了一个帖子,其中Rich Hickey解释了在上述情况下负责清除对头部的引用的机制的细节.这是:Rich Hickey在当地人清理