“失去理智”的解释在惰性序列中

发布于 2024-11-01 15:53:19 字数 308 浏览 8 评论 0原文

在 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

∞觅青森が 2024-11-08 15:53:19

详细说明 dfanRafał的答案,我已经采取了是时候使用 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)])

enter image description here

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)])

enter image description here

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:

enter image description here

旧竹 2024-11-08 15:53:19

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.

岁月染过的梦 2024-11-08 15:53:19

这里真正占据主导地位的是序列与 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 解释了负责在上述情况下清除对头部的引用的机制的详细信息。这是: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 with r to evaluate. In the second case, the existence of the not-yet-evaluated (first r) means that the evaluator needs to keep the binding to r.

To show the difference, this evaluates OK:

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

[99999999 5]

While this fails:

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

Even though the expression following (last r) ignores r, the evaluator is not that smart and keeps the binding to r, 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

怪我入戏太深 2024-11-08 15:53:19

有关技术说明,请访问 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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文