我对以下剪辑的行为感到困惑:
import Data.Int
import Data.Array.ST
import Control.Monad.ST
{-# INLINE fib #-}
fib _ 0 = return 0
fib _ 1 = return 1
fib c n = do
f1 <- memo c (fib c) (n-1)
f2 <- memo c (fib c) (n-2)
return (f1+f2)
newtype C a = C a
{-# INLINE memo #-}
memo (C a) f k = do
e <- readArray a k
if e == (-1)
then do
v <- f k
writeArray a k v
return v
else return e
evalFib :: Int -> Int
evalFib n = runST $ do
a <- newArray (0,n) (-1) :: ST s (STUArray s Int Int)
fib (C a) n
main = print $ evalFib 120000
使用-O2
进行编译时,堆栈溢出(显示正在使用的内存为20M)。令人困惑的部分是它实际上按预期工作(没有堆栈溢出和9M内存正在使用中),如果任何进行了以下更改:
Int64
代替Int
:(提供evalFib :: Int64 -> Int
和STUArray s Int64 Int
)。事实上,任何Int*
(Int32
,Int16
等)都可以做到这一点,Word
或Word*
; newtype C a
已从图片中移除; data C a = C !a
代替newtype
我试图了解这个行为:它是GHC /数组模块中的一个错误(它在7.4.2
和7.6.2
中显示相同的行为)还是应该以这种方式工作?
ghc array.hs -O2 -fext-core
编译它以查看核心产生的差异时,两个GHC版本都失败了“ghc:panic!('不可能'发生了)”。这里没有运气..
答案 0 :(得分:11)
你的初始程序,如你所说,堆栈溢出:
$ ghc -O2 A.hs --make -rtsopts
$ ./A +RTS -s
Stack space overflow: current size 8388608 bytes.
Use `+RTS -Ksize -RTS' to increase it.
21,213,928 bytes allocated in the heap
3,121,864 bytes copied during GC
5,400,592 bytes maximum residency (4 sample(s))
20,464 bytes maximum slop
20 MB total memory in use (0 MB lost due to fragmentation)
如果我们改为Int64,它可以正常工作:
$ time ./A
-2092835058118682368
./A 0.03s user 0.01s system 92% cpu 0.050 total
那么泄漏在哪里?
只是目视检查您的代码,有一个明显的问题:
您也可以从您看到的行为中推断出这一点:
newtype C a is removed from the picture;
data C a = C !a is used instead of newtype
所有服务器都将数组暴露给严格性分析器。
如果你在数组中使用fib严格:
{-# LANGUAGE BangPatterns #-}
{-# INLINE fib #-}
fib !_ 0 = return 0
fib !_ 1 = return 1
fib !c n = do
f1 <- memo c (fib c) (n-1)
f2 <- memo c (fib c) (n-2)
return (f1+f2)
然后它才起作用:
$ time ./A
-2092835058118682368
./A 0.03s user 0.01s system 89% cpu 0.052 total
那么为什么它会泄漏一种类型而不是另一种呢?你觉得Int64很幸运。但我认为这是一个“错误”,可能在各种数字类型的重写规则中。
查看简化器的输出,我们在Int64案例和Int案例中获得了相当不同的重写规则。由于底层函数通常由Int索引,因此您最终会使用公共Int类型执行不同的优化。在这种情况下,足以混淆严格性分析器。
与往常一样,一般规则适用:为编译器提供更多提示,并获得更好的代码。
答案 1 :(得分:5)
使用-O2
和-dsuppress-uniques
查看7.6.1的核心,无论我使用{{1},执行工作的函数Main.main_$spoly_$wa
在结构上(几乎)都是相同的}或int
作为索引类型。由于核心冗长而复杂,这里是Int64
输出:
diff
不同的索引类型,这些当然是不同的。
$ diff Int_core.dump-simpl Int64_core.dump-simpl
11,12c11,12
< (Data.Array.Base.STUArray s GHC.Types.Int GHC.Types.Int)
< (Main.C (Data.Array.Base.STUArray s GHC.Types.Int GHC.Types.Int))
---
> (Data.Array.Base.STUArray s GHC.Int.Int64 GHC.Types.Int)
> (Main.C (Data.Array.Base.STUArray s GHC.Int.Int64 GHC.Types.Int))
26,27c26,27
< (Data.Array.Base.STUArray s GHC.Types.Int GHC.Types.Int)
< (Main.C (Data.Array.Base.STUArray s GHC.Types.Int GHC.Types.Int)))
---
> (Data.Array.Base.STUArray s GHC.Int.Int64 GHC.Types.Int)
> (Main.C (Data.Array.Base.STUArray s GHC.Int.Int64 GHC.Types.Int)))
对于索引类型33,40d32
< l :: GHC.Types.Int
< [LclId]
< l = GHC.Types.I# sc } in
< let {
< u :: GHC.Types.Int
< [LclId]
< u = GHC.Types.I# sc1 } in
< let {
,GHC为越界索引产生更多信息性错误,因为它需要有效索引的下限和上限。 (Int
中未覆盖index
的默认实现。)
instance Ix Int64
不同的错误,45,46c37
< GHC.Types.False ->
< case poly_$w$j5 (GHC.Types.I# a) l u of wild2 { };
---
> GHC.Types.False -> case GHC.Arr.hopelessIndexError of wild1 { };
与indexError
。以下差异也仅涉及索引错误。
hopelessIndexError
现在又一次使用不同的索引类型:
49,50c40
< GHC.Types.False ->
< case poly_$w$j5 (GHC.Types.I# a) l u of wild2 { };
---
> GHC.Types.False -> case GHC.Arr.hopelessIndexError of wild2 { };
58c48
< case poly_$w$j4 y (GHC.Types.I# sc2) of wild3 { };
---
> case poly_$w$j3 y (GHC.Types.I# sc2) of wild4 { };
62c52
< case poly_$w$j4 y (GHC.Types.I# sc2) of wild5 { };
---
> case poly_$w$j3 y (GHC.Types.I# sc2) of wild5 { };
77,78c67
< GHC.Types.False ->
< case poly_$w$j3 (GHC.Types.I# a1) l u of wild6 { };
---
> GHC.Types.False -> case GHC.Arr.hopelessIndexError of wild6 { };
81,82c70
< GHC.Types.False ->
< case poly_$w$j3 (GHC.Types.I# a1) l u of wild7 { };
---
> GHC.Types.False -> case GHC.Arr.hopelessIndexError of wild7 { };
最后,110c98
< GHC.Types.Int
---
> GHC.Int.Int64
152c140
< s GHC.Types.Int GHC.Types.Int>)>)
---
> s GHC.Int.Int64 GHC.Types.Int>)>)
和0
获得了不同的顶级名称。
1
因此,完成实际工作的整个代码是完全相同的。然而,一个导致堆栈溢出(虽然只是177,178c165,166
< 0 -> (# sc5, lvl5 #);
< 1 -> (# sc5, lvl6 #)
---
> 0 -> (# sc5, lvl #);
> 1 -> (# sc5, lvl1 #)
足够[-K9M
就够了,-K8731K
不是])而另一个则没有。
差异确实是由索引错误引起的。具有-K8730K
索引的代码在Int
代码未分配的每个递归调用中分配两个盒装Int
,因为
Int64
带有两个对数组的引用。
使用更多堆栈,这些盒装Main.main_$spoly_$wa [Occ=LoopBreaker]
:: forall s.
GHC.Prim.Int#
-> GHC.Prim.Int#
-> GHC.Prim.Int#
-> GHC.Prim.MutableByteArray# s
-> (GHC.Prim.~#)
*
(Data.Array.Base.STUArray s GHC.Types.Int GHC.Types.Int)
(Main.C (Data.Array.Base.STUArray s GHC.Types.Int GHC.Types.Int))
-> GHC.Prim.Int#
-> GHC.Prim.State# s
-> (# GHC.Prim.State# s, GHC.Types.Int #)
必须进行垃圾回收,这会导致更大的GC数据。此外,索引错误的thunk比Int
thunk稍大。
现在,如果您通过
帮助编译器hopelessIndexError
)或其他一些方法,它产生更好的代码,管理没有给定参数的堆栈溢出,因为在worker中只有一个引用数组,因此盒装的分配
。不需要data C a = C !a
作为界限。
请注意,即使有编译器的帮助,此算法也会导致堆栈溢出稍大的参数。