“失去理智”的解释在惰性序列中
在 Clojure 编程语言中,为什么这段代码表现出色?
(let [r (range 1e9)] [(first r) (last r)])
虽然这个失败了:
(let [r (range 1e9)] [(last r) (first r)])
我知道这是关于“失去理智”的建议,但你能向我解释一下吗?我还无法消化它。
更新:
很难选出正确的答案,两个答案的信息量惊人。
注意:代码片段来自“The Joy of Clojure”。
In Clojure programming language, why this code passes with flying colors?
(let [r (range 1e9)] [(first r) (last r)])
While this one fails:
(let [r (range 1e9)] [(last r) (first r)])
I know it is about "Losing your head" advice but would you please explain it to me? I'm not able to digest it yet.
UPDATE:
It is really hard to pick the correct answer, two answers are amazingly informative.
Note: Code snippets are from "The Joy of Clojure".
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
详细说明 dfan 和 Rafał的答案,我已经采取了是时候使用 YourKit 分析器运行这两个表达式了。
看到 JVM 的工作是很有趣的。第一个程序对 GC 非常友好,以至于 JVM 在管理内存方面确实表现出色。
我画了一些图表。
GC 友好: (let [r (range 1e9)] [(first r) (last r)])
该程序运行内存非常低;总体而言,不到6兆字节。如前所述,它对 GC 非常友好,它会进行大量回收,但只使用很少的 CPU。
头架:(let [r (range 1e9)] [(last r) (first r)])
这个非常消耗内存。它的 RAM 高达 300 MB,但这还不够,程序无法完成(JVM 不到一分钟后就终止了)。 GC 占用了 90% 的 CPU 时间,这表明它拼命地尝试释放任何可以释放的内存,但找不到任何内存(收集到的对象很少甚至没有)。
编辑 第二个程序内存不足,触发了堆转储。对此转储的分析表明,70% 的内存是 java.lang.Integer 对象,无法收集。这是另一个屏幕截图:
To elaborate on dfan and Rafał's answers, I've taken the time to run both expressions with the YourKit profiler.
It's fascinating to see the JVM at work. The first program is so GC-friendly that the JVM really shines at managing its memory.
I drew some charts.
GC friendly: (let [r (range 1e9)] [(first r) (last r)])
This program runs very low on memory; overall, less than 6 megabytes. As stated earlier, it is very GC friendly, it makes a lot of collections, but uses very little CPU for that.
Head holder: (let [r (range 1e9)] [(last r) (first r)])
This one is very memory hungry. It goes up to 300 MB of RAM, but that's not enough and the program does not finish (the JVM dies less than one minute later). The GC takes up to 90% of CPU time, which indicates it desperately tries to free any memory it can, but cannot find any (the objects collected are very little to none).
Edit The second program ran out of memory, which triggered a heap dump. An analysis of this dump shows that 70% of the memory is java.lang.Integer objects, which could not be collected. Here's another screenshot:
range
根据需要生成元素。在
(let [r (range 1e9)] [(first r) (last r)])
的情况下,它获取第一个元素 (0),然后生成 10 亿 - 2 个元素,抛出将它们按顺序取出,然后获取最后一个元素 (999,999,999)。它不需要保留序列的任何部分。在
(let [r (range 1e9)] [(last r) (first r)])
的情况下,它生成十亿个元素以便能够评估(last r) )
,但它还必须保留其生成的列表的开头,以便稍后评估(first r)
。所以它不能在运行过程中丢弃任何东西,并且(我推测)内存不足。range
generates elements as needed.In the case of
(let [r (range 1e9)] [(first r) (last r)])
, it grabs the first element (0), then generates a billion - 2 elements, throwing them out as it goes, and then grabs the last element (999,999,999). It never has any need to keep any part of the sequence around.In the case of
(let [r (range 1e9)] [(last r) (first r)])
, it generates a billion elements in order to be able to evaluate(last r)
, but it also has to hold on to the beginning of the list it's generating in order to later evaluate(first r)
. So it's not able to throw anything away as it goes, and (I presume) runs out of memory.这里真正占据主导地位的是序列与
r
的绑定(不是已经评估的(first r)
,因为你无法根据其值来评估整个序列。 )在第一种情况下,当计算
(last r)
时,绑定不再存在,因为没有更多带有r
的表达式可供计算。在第二种情况下,尚未评估的(first r)
的存在意味着评估器需要保持与r
的绑定。为了显示差异,此计算结果为 OK:
而此计算失败:
即使
(last r)
后面的表达式忽略r
,评估器也不是那个 智能并保持与r
的绑定,从而保持整个序列。编辑:我发现了一篇文章,其中 Rich Hickey 解释了负责在上述情况下清除对头部的引用的机制的详细信息。这是:Rich Hickey 谈当地人清理
What really holds the head here is the binding of the sequence to
r
(not the already-evaluated(first r)
, since you cannot evaluate the whole sequence from its value.)In the first case the binding no longer exists when
(last r)
is evaluated, since there are no more expressions withr
to evaluate. In the second case, the existence of the not-yet-evaluated(first r)
means that the evaluator needs to keep the binding tor
.To show the difference, this evaluates OK:
While this fails:
Even though the expression following
(last r)
ignoresr
, the evaluator is not that smart and keeps the binding tor
, thus keeping the whole sequence.Edit: I have found a post where Rich Hickey explains the details of the mechanism responsible for clearing the reference to the head in the above cases. Here it is: Rich Hickey on locals clearing
有关技术说明,请访问 http://clojure.org/lazy。 建议在
不要悬着头
部分提到for a technical description, go to http://clojure.org/lazy. the advice is mentioned in the section
Don't hang (onto) your head