简短概述
在阅读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
大致分配相同的内存?这与列表融合有关吗? - 还是我的基准测试存在缺陷?
Int
没有被优化掉,所以fTails
也会为它们进行 O(n^2) 的分配。必须查看核心代码才能确定(我手头没有 ghc)。 - user395760(ಠ_ರ) ?
你是在使用和我相同的基准测试吗?你使用的 GHC 版本是哪个?我正在使用 GHC 7.10.1。 - jub0bs+RTS -s
时,我只是将其中一个注释掉。使用RTS -p
时,我的结果与你的类似。 - András Kovács