【发布时间】:2015-06-06 06:06:49
【问题描述】:
TL;博士
在阅读了 Okasaki 的 Purely Functional Data Structures 中关于 persistence 的文章并查看了他关于单链表的说明性示例(这就是 Haskell 的列表是如何实现的)之后,我想知道Data.List 的inits 和tails 的空间复杂性...
在我看来
-
tails的空间复杂度在其参数长度上是线性,并且 -
inits的空间复杂度在其参数长度上是二次,
但一个简单的基准表明并非如此。
基本原理
使用tails,可以共享原始列表。计算tails xs 只需沿着列表xs 前进并创建一个指向该列表每个元素的新指针;无需在内存中重新创建 xs 的一部分。
相比之下,因为inits xs的每个元素“以不同的方式结束”,所以不可能有这样的共享,xs所有可能的前缀都必须在内存中从头开始重新创建。
基准测试
下面的简单基准表明两个函数之间的内存分配没有太大差异:
-- Main.hs
import Data.List (inits, tails)
main = do
let intRange = [1 .. 10 ^ 4] :: [Int]
print $ sum intRange
print $ fInits intRange
print $ fTails intRange
fInits :: [Int] -> Int
fInits = sum . map sum . inits
fTails :: [Int] -> Int
fTails = sum . map sum . tails
编译我的Main.hs文件后
ghc -prof -fprof-auto -O2 -rtsopts Main.hs
正在运行
./Main +RTS -p
Main.prof 文件报告以下内容:
COST CENTRE MODULE %time %alloc
fInits Main 60.1 64.9
fTails Main 39.9 35.0
分配给fInits的内存和分配给fTails的内存数量级相同...嗯...
发生了什么事?
- 我关于
tails(线性)和inits(二次)空间复杂性的结论是否正确? - 如果是这样,为什么 GHC 为
fInits和fTails分配大致一样多的内存? list fusion和这个有关系吗? - 还是我的基准测试有缺陷?
【问题讨论】:
-
我唯一的猜测是:中间的
Ints 没有被优化掉,所以fTails也为这些分配了 O(n^2) 。必须查看核心来检查(我手头没有 ghc)。 -
在运行
fInits或fTails之前,您可能应该强制列表 (print $ sum intRange)。 -
@delnan 谢谢。我还不习惯检查核心,但我会调查一下。
-
@Cirdec 完成。没有变化。
-
忽略我的 GHC 7.8.3 结果(以及其他任何人)。 GHC7.8.3 has a bug where inits is very slow。它已在 7.8.4 中修复。
标签: haskell profiling singly-linked-list space-complexity