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 不同的是,这些对象可能表示挂起的计算,也称为挂起的计算。thunks, 例如 (g x y
/p2
)在我们的例子中。如果不进行优化,这种执行模型的效率相当低,但是有一些方法可以避免其中的许多开销。
GHC 做了很多内联和拆箱工作。内联消除了函数调用开销,并且通常可以实现进一步的优化。拆箱意味着改变调用约定,在上面的例子中我们可以通过42
直接而不是创建堆对象p1
.
严格性分析可以确定是否保证对某个参数进行评估。在这种情况下,我们不需要创建 thunk,而是完全评估表达式,然后将最终结果作为参数传递。
小对象(目前只有8bitChar
s and Int
s) 被缓存。 也就是说,不是为每个对象分配一个新指针,而是返回一个指向缓存对象的指针。 即使该对象最初是在堆上分配的,垃圾收集器稍后也会删除它们的重复项(只有小Int
s and Char
s)。由于对象是不可变的,所以这是安全的。
有限的逃逸分析。对于本地函数,一些参数可以在堆栈上传递,因为在外部函数返回时它们已知为死代码。
Edit:有关(更多)更多信息,请参阅“在库存硬件上实现惰性函数语言:无脊柱无标签 G 机” http://research.microsoft.com/apps/pubs/default.aspx?id=67083。本文使用“push/enter”作为调用约定。较新版本的 GHC 使用“eval/apply”调用约定。有关该转换的权衡和原因的讨论,请参阅“如何快速咖喱:push/enter 与 eval/apply” http://research.microsoft.com/en-us/um/people/simonpj/papers/eval-apply/
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)