Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell init和tails的空间复杂性是什么? TL;博士_Haskell_Profiling_Singly Linked List_Space Complexity - Fatal编程技术网

Haskell init和tails的空间复杂性是什么? TL;博士

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

在阅读了关于冈崎纯功能数据结构中持久性的文章,并回顾了他关于单链表的示例(这就是Haskell列表的实现方式)之后,我对
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
    分配的内存大致相同?列表融合与此有关吗
  • 还是我的基准有缺陷
Haskell报告中的
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.4
inits
现在可以在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中被修正。非常全面的答案。谢谢