Haskell init和tails的空间复杂性是什么? TL;博士
在阅读了关于冈崎纯功能数据结构中持久性的文章,并回顾了他关于单链表的示例(这就是Haskell列表的实现方式)之后,我对Haskell init和tails的空间复杂性是什么? TL;博士,haskell,profiling,singly-linked-list,space-complexity,Haskell,Profiling,Singly Linked List,Space Complexity,在阅读了关于冈崎纯功能数据结构中持久性的文章,并回顾了他关于单链表的示例(这就是Haskell列表的实现方式)之后,我对Data.List的inits和tails的空间复杂性感到疑惑 在我看来 tails的空间复杂度在其参数长度上是线性的,并且 inits的空间复杂度在其参数长度上是二次的 但一个简单的基准表明情况并非如此 根本原因 使用tails,可以共享原始列表。计算trails xs只需沿着列表xs走,并创建指向该列表中每个元素的新指针;无需在内存中重新创建部分xs 相反,由于init
Data.List
的inits
和tails
的空间复杂性感到疑惑
在我看来
的空间复杂度在其参数长度上是线性的,并且tails
的空间复杂度在其参数长度上是二次的inits
tails
,可以共享原始列表。计算trails xs
只需沿着列表xs
走,并创建指向该列表中每个元素的新指针;无需在内存中重新创建部分xs
相反,由于inits xs
的每个元素“以不同的方式结束”,因此不可能有这样的共享,并且xs
的所有可能前缀必须在内存中从头开始重新创建
基准
下面的简单基准测试表明,这两个函数在内存分配方面没有太大区别:
--Main.hs
导入数据列表(初始、尾部)
main=do
让内部消息=[1..10^4]:[Int]
在内部打印$sum
打印$fInits内部消息
打印$fTails内部邮件
限制::[Int]->Int
fInits=总和。映射和。初始化
fTails::[Int]->Int
fTails=总和。映射和。尾巴
使用编译我的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
分配给finites
的内存与分配给fTails
的内存具有相同的数量级。。。哼
发生了什么事?
- 我关于
(线性)和tails
(二次)的空间复杂性的结论正确吗inits
- 如果是这样,为什么GHC为
和finites
分配的内存大致相同?列表融合与此有关吗fTails
- 还是我的基准有缺陷李>
inits
的实现速度非常慢,与Base4.7.0.1(GHC7.8.3)之前使用的实现相同或几乎相同。特别是,fmap
应用程序以递归方式叠加,因此强制结果的后续元素变得越来越慢
inits [1,2,3,4] = [] : fmap (1:) (inits [2,3,4])
= [] : fmap (1:) ([] : fmap (2:) (inits [3,4]))
= [] : [1] : fmap (1:) (fmap (2:) ([] : fmap (3:) (inits [4])))
....
Bertram Felgenhauer探索的最简单的渐近最优实现是基于应用具有连续较大参数的take
:
inits xs = [] : go (1 :: Int) xs where
go !l (_:ls) = take l xs : go (l+1) ls
go _ [] = []
Felgenhauer使用了一个私有的、非融合版本的take
,能够从中获得一些额外的性能,但它仍然没有达到它所能达到的速度
在大多数情况下,以下非常简单的实现速度要快得多:
inits = map reverse . scanl (flip (:)) []
在一些奇怪的情况下(比如
map head.inits
),这个简单的实现是渐进非最优的。因此,我使用相同的技术编写了一个版本,但基于Chris Okasaki的银行家队列,它是渐近最优的,并且几乎同样快。Joachim Breitner进一步优化了它,主要是通过使用严格的scanl'
而不是通常的scanl
,这个实现进入了GHC 7.8.4inits
现在可以在O(n)时间内生成结果的脊椎;强制执行整个结果需要O(n^2)时间,因为不同的初始段之间无法共享任何conse。如果您想要非常快的inits
和tails
,那么最好使用Data.Sequence
;路易斯·瓦瑟曼的实施。另一种可能是使用Data.Vector
——它可能会对这些东西使用切片。我唯一的猜测是:中间Int
s没有被优化掉,因此fTails
也会对它们进行O(n^2)分配。您必须查看核心以检查这一点(我手头没有ghc)。在运行fInits
或fTails
@delnan谢谢之前,您可能应该强制执行列表(print$sum intRange
)。“我还不习惯检查核心,但我会调查的。”Cirdec说。无更改。忽略我的GHC 7.8.3结果(以及任何其他人)。GHC。它在7.8.4中被修正。非常全面的答案。谢谢