13

我正在设计一种语言。首先,我想决定生成什么代码。该语言将具有类似于 javascript 的词法闭包和基于原型的继承。但我不是 gc 的粉丝,并尽量避免。那么问题来了:有没有一种优雅的方式来实现闭包,而无需在堆上分配堆栈帧并将其留给垃圾收集器?

我的第一个想法:

  1. 使用引用计数和垃圾收集周期(我不太喜欢这个)
  2. 使用意大利面条堆栈(看起来非常低效)
  3. 将闭包的形成限制在某些上下文中,这样我就可以摆脱返回地址堆栈和本地堆栈。

我不会使用高级语言或遵循任何调用约定,所以我可以随心所欲地粉碎堆栈。

(编辑:我知道引用计数是垃圾收集的一种形式,但我在其更常见的含义中使用 gc)

4

13 回答 13

13

如果您可以通过不使用 GC 来解释您试图避免的事情,这将是一个更好的问题。我相信您知道,大多数提供词法闭包的语言都将它们分配在堆上,并允许它们在创建它们的激活记录中保留对变量绑定的引用。

我知道该方法的唯一替代方法是gcc嵌套函数的用途:为函数创建一个蹦床并将其分配到堆栈上。但正如 gcc 手册所说:

如果您在包含函数退出后尝试通过其地址调用嵌套函数,那么一切都会崩溃。如果您在包含范围级别退出后尝试调用它,并且如果它引用了一些不再在范围内的变量,那么您可能很幸运,但冒险并不明智。但是,如果嵌套函数没有引用超出范围的任何内容,那么您应该是安全的。

简短的版本是,您有三个主要选择:

  • 在堆栈上分配闭包,并且在包含函数退出后不允许使用它们。
  • 在堆上分配闭包,并使用某种垃圾收集。
  • 做原创研究,可能从 ML、Cyclone 等所拥有的区域开始。
于 2008-09-18T02:00:22.540 回答
9

I understand that I'm very late, but I stumbled upon this question by accident.

I believe that full support of closures indeed requires GC, but in some special cases stack allocation is safe. Determining these special cases requires some escape analysis. I suggest that you take a look at the BitC language papers, such as Closure Implementation in BitC. (Although I doubt whether the papers reflect the current plans.) The designers of BitC had the same problem you do. They decided to implement a special non-collecting mode for the compiler, which denies all closures that might escape. If turned on, it will restrict the language significantly. However, the feature is not implemented yet.

I'd advise you to use a collector - it's the most elegant way. You should also consider that a well-built garbage collector allocates memory faster than malloc does. The BitC folks really do value performance and they still think that GC is fine even for the most parts of their operating system, Coyotos. You can migitate the downsides by simple means:

  • create only a minimal amount of garbage
  • let the programmer control the collector
  • optimize stack/heap use by escape analysis
  • use an incremental or concurrent collector
  • if somehow possible, divide the heap like Erlang does

Many fear garbage collectors because of their experiences with Java. Java has a fantastic collector, but applications written in Java have performance problems because of the sheer amount of garbage generated. In addition, a bloated runtime and fancy JIT compilation is not really a good idea for desktop applications because of the longer startup and response times.

于 2009-04-26T03:14:59.280 回答
9

该线程可能会有所帮助,尽管此处的某些答案已经反映了那里的答案。

一张海报提出了一个很好的观点:

似乎您希望“在没有真正垃圾收集的情况下”对闭包进行垃圾收集。请注意,闭包可用于实现 cons 单元。所以你的问题似乎是关于“没有真正的垃圾收集”的垃圾收集——有丰富的相关文献。将问题限制为闭包并没有真正改变它。

所以答案是:,没有优雅的闭包方式,也没有真正的 GC。您可以做的最好的事情是一些黑客攻击将您的闭包限制为特定类型的闭包。如果您有适当的 GC,所有这些都是不必要的。

所以,我的问题反映了这里的其他一些问题——你为什么不想实现 GC?一个简单的标记+扫描或停止+复制大约需要 2-300 行 (Scheme) 代码,并且在编程工作方面并没有那么糟糕。在使您的程序变慢方面:

  1. 您可以实现更复杂的 GC,它具有更好的性能。
  2. 想想你的语言中的所有内存泄漏程序都不会受到影响。
  3. 使用可用的 GC 进行编码是一件幸事。(想想 C#、Java、Python、Perl 等……与 C++ 或 C)。
于 2009-01-15T14:43:48.037 回答
4

使用引用计数和垃圾收集周期(我不太喜欢这个)

可以设计你的语言,使之没有循环:如果你只能制造新的对象而不改变旧的对象,如果制造一个对象不能制造一个循环,那么循环就不会出现。Erlang 本质上就是这样工作的,尽管在实践中它确实使用了 GC。

于 2008-10-11T10:06:24.360 回答
4

C++ 0x 规范定义了没有垃圾收集的 lambda。简而言之,在 lambda 闭包包含不再有效的引用的情况下,规范允许非确定性行为。例如(伪语法):

(int)=>int create_lambda(int a)
{
    return { (int x) => x + a }
}

create_lambda(5)(4)    // undefined result

此示例中的 lambda 指的a是在堆栈上分配的变量 ( )。但是,该堆栈帧已被弹出,并且在函数返回后不一定可用。在这种情况下,它可能会工作并9作为结果返回(假设编译器语义健全),但没有办法保证它。

如果您要避免垃圾收集,那么我假设您还允许显式堆与堆栈分配和(可能)指针。如果是这种情况,那么您可以像 C++ 那样做,并假设使用您的语言的开发人员足够聪明,可以使用 lambdas 发现问题情况并显式复制到堆中(就像您返回一个在其中合成的值一样)一个函数)。

于 2008-09-18T02:08:14.813 回答
3

如果您有精确复制 GC 的机制,您可以最初在堆栈上分配并复制到堆并更新指针,如果您在退出时发现指向此堆栈帧的指针已转义。这样,只有当您确实捕获了包含此堆栈帧的闭包时,您才需要付费。这是否有帮助或有害取决于您使用闭包的频率以及它们捕获的数量。

您也可以研究 C++0x 的方法 ( N1968 ),尽管正如人们对 C++ 所期望的那样,它包括依靠程序员指定要复制的内容和引用的内容,如果您弄错了,您只会获得无效访问。

于 2008-09-18T02:04:56.397 回答
2

您可以假设所有闭包最终都将被调用一次。现在,当调用闭包时,您可以在闭包返回时进行清理。

你打算如何处理返回的对象?它们必须在某个时候进行清理,这与闭包的问题完全相同。

于 2009-01-15T14:17:30.277 回答
2

或者根本不做GC。在某些情况下,最好忘记内存泄漏,并在完成后让进程清理它。

根据您对 GC 的疑虑,您可能会害怕定期 GC 扫描。在这种情况下,您可以在项目超出范围或指针更改时执行选择性 GC。我不确定这会有多贵。

@艾伦

如果在包含函数退出时不能使用闭包,那么闭包有什么用?据我了解,这就是闭包的全部意义所在。

于 2008-09-18T02:05:42.370 回答
2

那么问题来了:有没有一种优雅的方式来实现闭包,而无需在堆上分配堆栈帧并将其留给垃圾收集器?

GC 是一般情况下的唯一解决方案。

于 2013-07-17T19:41:37.387 回答
1

迟到总比不到好?

您可能会发现这很有趣:差异执行

这是一种鲜为人知的控制结构,它的主要用途是对用户界面进行编程,包括在使用时可以动态更改的用户界面。它是模型-视图-控制器范式的重要替代方案。

我提到它是因为有人可能认为这样的代码会严重依赖闭包和垃圾收集,但控制结构的副作用是它消除了这两者,至少在 UI 代码中是这样。

于 2010-06-01T18:44:04.460 回答
0

创建多个堆栈?

于 2008-10-21T13:39:31.193 回答
0

我读过机器学习的最后一个版本只很少使用 GC

于 2009-01-15T14:34:14.297 回答
0

我想如果这个过程很短,这意味着它不能使用太多的内存,那么 GC 是不必要的。这种情况类似于担心堆栈溢出。不要嵌套太深,也不能溢出;不要运行太久,你不需要GC。清理变成了简单地回收您预先分配的大区域的问题。即使是较长的进程也可以分成更小的进程,这些进程预先分配了自己的堆。例如,这适用于事件处理程序。如果您正在编写编译器,它就不能很好地工作;在这种情况下,GC 肯定不会有太大的障碍。

于 2010-11-02T08:51:27.113 回答