在Clojure编程语言中,为什么这段代码以优异的成绩通过?
(let [r (range 1e9)] [(first r) (last r)])虽然这个失败了:
(let [r (range 1e9)] [(last r) (first r)])我知道这是关于“失去理智”的建议,但你能给我解释一下吗?我还不能消化它。
更新:
真的很难选择正确的答案,两个答案提供了令人惊讶的信息。
注:代码片段来自“闭合的Joy”。
发布于 2011-04-18 11:15:01
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)。因此,它不能在运行过程中丢弃任何东西,并且(我假设)会耗尽内存。
发布于 2011-04-18 20:32:28
为了详细说明dfan和Rafał的答案,我花时间用YourKit分析器运行了这两个表达式。
看到JVM在工作是一件很有趣的事情。第一个程序对GC非常友好,JVM在管理内存方面表现出色。
我画了一些图表。
GC友好:(让r(范围1e9))

这个程序的内存非常低;总体来说,不到6兆字节。如前所述,它对GC非常友好,它进行了大量的收集,但只使用了很少的CPU。
头固定器:(字母r(范围1e9))

这是一个非常需要内存的版本。它会占用300MB的RAM,但这还不够,程序无法完成( JVM在不到一分钟后就死了)。GC占用了高达90%的CPU时间,这表明它拼命地尝试释放所有可以释放的内存,但找不到任何内存(收集的对象非常少甚至没有)。
编辑第二个程序内存不足,这触发了堆转储。对此转储的分析表明,70%的内存是无法收集的java.lang.Integer对象。这是另一个截图:

发布于 2011-04-18 20:19:27
这里真正关键的是序列到r的绑定(而不是已经计算过的(first r),因为您不能根据它的值来评估整个序列)。
在第一种情况下,当计算(last r)时,绑定不再存在,因为不再有包含r的表达式需要计算。在第二种情况下,尚未计算的(first r)的存在意味着计算器需要保持与r的绑定。
为了显示不同之处,计算结果为OK:
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 on locals clearing
https://stackoverflow.com/questions/5698122
复制相似问题