haskell - Haskell 编译器如何决定是在堆上分配还是在栈上分配?

标签 haskell memory-management compiler-construction heap-memory stack-memory

Haskell 没有显式内存管理功能,所有对象都是按值传递的,因此也没有明显的引用计数或垃圾收集。 Haskell 编译器通常如何决定是生成在堆栈上分配的代码还是在堆上为给定变量分配的代码?它是否会始终如一地在不同的调用站点为同一函数分配相同的变量?当它分配时,它如何决定何时释放内存?堆栈分配和释放是否仍以与 C 中相同的函数进入/退出模式执行?

最佳答案

当你调用这样的函数时

f 42 (g x y)

那么运行时行为类似于以下内容:
p1 = malloc(2 * sizeof(Word))
p1[0] = &Tag_for_Int
p1[1] = 42
p2 = malloc(3 * sizeof(Word))
p2[0] = &Code_for_g_x_y
p2[1] = x
p2[2] = y
f(p1, p2)

也就是说,参数通常作为指向堆上对象的指针传递,就像在 Java 中一样,但与 Java 不同的是,这些对象可能表示挂起的计算,也就是 thunk,例如我们示例中的 (g x y/p2 )。如果没有优化,这种执行模型效率很低,但有一些方法可以避免这些开销。
  • GHC 做了很多内联和拆箱。内联消除了函数调用开销,并且通常可以进行进一步的优化。拆箱意味着改变调用约定,在上面的例子中我们可以通过 42直接而不是创建堆对象p1 .
  • 严格性分析找出一个论点是否保证被评估。在这种情况下,我们不需要创建 thunk,而是完全评估表达式,然后将最终结果作为参数传递。
  • 缓存小对象(当前只有 8 位 CharInt )。也就是说,不是为每个对象分配一个新的指针,而是返回一个指向缓存对象的指针。即使对象最初是在堆上分配的,垃圾收集器稍后也会对它们进行重复数据删除(只有小的 Int s 和 Char s)。由于对象是不可变的,因此这是安全的。
  • 有限的逃逸分析。对于局部函数,一些参数可能会在堆栈上传递,因为在外部函数返回时它们已被认为是死代码。

  • 编辑 : 如需(更多)更多信息,请参阅 "Implementing Lazy Functional Languages on Stock Hardware: The Spineless Tagless G-machine" .本文使用“push/enter”作为调用约定。较新版本的 GHC 使用“eval/apply”调用约定。有关该转换的权衡和原因的讨论,请参阅 "How to make a fast curry: push/enter vs eval/apply"

    关于haskell - Haskell 编译器如何决定是在堆上分配还是在栈上分配?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5132350/

    相关文章:

    c - 基本内存地址混淆

    c++ - typeid 何时可以为同一类型返回不同的 type_info 实例?

    haskell - 在这种情况下如何使用 mapM_

    haskell - 在 Windows 上为 Haskell 安装 SDL (GHC)

    qt - 如果 QObjects 同时是类(class)成员和 child ,为什么不删除两次?

    MySQL 导入 - [大文件 ~ 350 GB] - 两周且仍在运行

    c# - 为什么编译器不能从用法中推断出这个类型参数

    C++ CPU 寄存器使用

    haskell - 并行 IO 导致终端出现随机文本输出

    haskell - 一种复制方法,例如replicateM?