鉴于Haskell运行时可以获得大量类型信息,为什么不能避免运行GC进行清理呢?在编译代码时,应该能够找出所有用法并插入适当的alloc/release调用,对吧?这将避免运行时GC的开销。
鉴于Haskell运行时可以获得大量类型信息,为什么不能避免运行GC进行清理呢?在编译代码时,应该能够找出所有用法并插入适当的alloc/release调用,对吧?这将避免运行时GC的开销。
有理由质疑函数式编程语言是否能通过跟踪使用情况来减少垃圾回收。尽管由于条件分支而导致某些数据是否可以安全丢弃的一般问题是不可判定的,但是静态工作更加可行,并且可以找到更多直接释放内存的机会。
值得注意的是,Martin Hofmann和移动资源保证项目团队在类型导向的内存(de/re)分配方面做出了重大贡献。然而,使他们的东西起作用的是Haskell在其类型系统中没有的东西——线性性。如果您知道函数的输入数据对于计算的其他部分是“秘密”的,那么您可以重新分配它们占用的内存。 MRG的特别好处在于,它管理一个实际的兑换率,将一种类型的释放与另一种类型的分配联系起来,这在纯函数外表下变成了老式的指针混淆。事实上,许多可爱的简化变异算法(例如指针反转遍历,覆盖尾指针构造等)可以使用这些技术变得纯函数化(并检查nasty bugs)。
实际上,资源的线性类型给出了对可能有助于减少GC的使用分析的保守但机械可检查的近似。有趣的问题包括如何将这种处理与通常的持久交易清理混合在一起(故意副词选择)。在递归计算中,似乎有相当多的中间数据结构具有初始的单线程阶段,然后在计算完成时被共享或丢弃。 可能可以减少此类过程生成的垃圾。
简而言之,有良好的类型化使用分析方法可以减少GC,但是Haskell现在具有不适合特别实用此目的的类型信息。
基于区域的内存管理是C和C ++程序员经常手动编程的内容:分配一块内存(“区域”,“arena”等),在其中分配单个数据,使用它们,并在不再需要任何单个数据时释放整个块。上世纪90年代,Tofte、Aiken和其他人(包括我和我们各自的同事)的工作表明,可以通过“区域推断”静态地自动推断出区域分配和释放点,以保证块从未过早地被释放,在实践中足够早地避免长时间持有最后一个数据后释放大量内存。例如,ML Kit for Regions是基于区域推断的完整标准ML编译器。在其最终版本中,它与内部区域垃圾收集相结合:如果静态推断显示存在长期使用的区域,则在其中使用垃圾收集。你可以同时拥有你的蛋糕并吃掉它:对于长期存活的数据,你拥有垃圾收集,但大量数据像堆栈一样管理,即使通常会进入堆。
请考虑以下伪代码:
func a = if some_computation a
then a
else 0
要知道在调用func a
之后,a
是否为垃圾,编译器必须能够知道some_computation a
的结果。如果它可以在一般情况下做到这一点(这需要解决停机问题),那么根本不需要为此函数生成代码,更不用说垃圾收集了。类型信息是不足的。
a
是无用的吗?我们只需要在该分支中插入调用collect的语句即可。 - Asad-ullah Khana
的引用怎么办?例如,它是从某个具有对它的引用的地方传递进来的。我9年前写得不是很好,但重点是在运行y = func x
之后,y
可能是对值x的引用,也可能不是。您需要识别所有实际上引用x的东西,以知道在哪里静态编译调用以收集x,而不仅仅是所有可能引用x的东西。 - Ben使用惰性求值确定对象的生存周期并不容易。JHC编译器具有(或曾经拥有)区域内存管理,试图通过在生命周期结束时进行解除分配来释放内存。
我也很好奇你所说的确定性内存管理是什么意思。
类型信息主要与编译时有关,而内存管理是运行时的事情,因此我认为它们彼此之间没有关联。
runST
来进行某种形式的作用域分配。虽然这与通过代码分析推断这些属性的相反! - C. A. McCannrunST
技巧可能让我们堆叠分配区域,但并不明显堆叠是我们唯一需要的结构。在这个充满扩散互联处理器的新世界中,我们确实可以使用类型来说明东西在哪里(以及何时!)以及它是什么。我们可以想象有一个固定寿命数据区域,在那里分配的所有东西,如果没有明确导出到其他地方,就会在最后被压缩。有趣的时代在前方! - pigworker