如何在功能语言中使用大型数据结构时减少垃圾收集时间?
(我使用的是Racket,但问题适用于任何具有垃圾收集器的功能导向语言。)
函数式编程的核心习惯是你设计函数来处理数据的副本,并返回处理后的数据,而不是远程改变数据结构。你不必担心额外的复制,因为垃圾收集器来了并恢复了不再使用的内存。
这就是它的伟大之处。但是当我开始编写处理更大数据结构的程序时,我发现垃圾收集占用了更多的总运行时间(比如25-35%,而不是我发现的10-15%)小结构)。
这并不奇怪,因为每个函数调用都会复制更多数据,因此需要收集更多垃圾。
但它也使速度提升变得更加困难,因为垃圾收集器会占用更多的运行时间,而垃圾收集器基本上不在您的控制范围之内。
显而易见的解决方案是避免复制数据。但是这会使你重新从远处变异数据,这与功能成语相矛盾。 (虽然我知道这可以在Racket中通过使用盒装值,甚至参数来完成。)
除此之外,我还有三种选择:
这些有效的方法吗?我还有其他人可以忽略吗?
答案 0 :(得分:4)
有一些功能数据结构旨在降低复制成本 - 例如,如果一个函数改变树的一个分支,那么新树将共享来自未受影响的分支的节点,只有变异的分支才需要被复制。
克里斯·冈崎的Purely Functional Data Structures是我所知道的最好的论文,但最近可能还有一些我不知道的研究(例如,ctrie,我只通过维基百科知道。
答案 1 :(得分:1)
这不是一个完美的答案,但它比我发现的其他资源更具主题。本文“Typed Racket中的功能数据结构”讨论了在某些情况下比标准列表表现更好的替代功能数据结构,并给出了包含垃圾收集时间的特定时序结果(HT Greg Hendershott):
http://www.ccs.neu.edu/racket/pubs/sfp10-kth.pdf
这是实现的代码:
答案 2 :(得分:0)
产生大量不必要的垃圾当然是坏事。如果探查器显示您正在分配大量内存,并且GC正在成功恢复大量内存,那么您应该考虑减少垃圾。这是你(1)进来的地方:
设计您的数据结构,以便更紧凑地编码信息。
这一般来说非常重要。如果GC时间对性能造成影响,请尝试使用更紧凑的数据结构。如果GC时间不会影响性能,请尝试使用更紧凑的数据结构。紧凑的数据结构提高了GC性能和缓存利用率。如果您可以用更少的字节将更多信息填充到CPU缓存中,那么通常可以显着提高速度。
另一方面,你的(2)和(3)看起来很腥,并向我建议你可能不清楚如何在记忆中实际表示价值。将结构传递给函数不通常将其复制到典型的函数式语言实现中。也就是说,"拉出你需要处理的数据子集"可以有意义,因为如果不需要其余部分,你可以更快地把它变成垃圾,这很好。最后一点是25-35%的GC时间可能听起来不错,但它并不像它那样糟糕。我看到的最糟糕的情况是“弱世代假设”#34;被侵犯了。也就是说,当你分配一堆内存时, not 会很快变成垃圾。这很糟糕,因为垃圾收集器不断尝试收集垃圾,追踪越来越多的非垃圾,而实际上并没有实现太多。在这些情况下,我个人看到GC时间高达75%左右,但如果情况可能更糟,我不会感到惊讶。