从7.6.1版本的核心代码来看,在使用-O2
和-dsuppress-uniques
选项后,执行实际任务的函数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 {
对于索引类型Int
,GHC会产生更具信息量的错误提示,以便处理越界索引,因为它需要有效索引的下限和上限。(在instance Ix Int64
中,未覆盖index
的默认实现。)
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
---
> GHC.Types.False -> case GHC.Arr.hopelessIndexError of wild2 { };
58c48
< case poly_$w$j4 y (GHC.Types.I
---
> case poly_$w$j3 y (GHC.Types.I
62c52
< case poly_$w$j4 y (GHC.Types.I
---
> case poly_$w$j3 y (GHC.Types.I
77,78c67
< GHC.Types.False ->
< case poly_$w$j3 (GHC.Types.I
---
> GHC.Types.False -> case GHC.Arr.hopelessIndexError of wild6 { };
81,82c70
< GHC.Types.False ->
< case poly_$w$j3 (GHC.Types.I
---
> 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 #)
在代码中有两个对数组的引用,在栈中占据更多空间,并且这些装箱的Int
必须进行垃圾回收,这导致了更大的GC数字。此外,索引错误的thunk比hopelessIndexError
thunk稍微大一点。
现在,如果您通过以下方式帮助编译器:
- 删除newtype包装器
- 使函数严格遵循数组(通过叹号模式或
data C a = C !a
)
或者其他一些方法,它将产生更好的代码,可以在给定参数的情况下不使用堆栈溢出,因为在worker中只有一个对数组的引用,因此不需要为边界分配装箱的Int
。
请注意,即使在编译器的帮助下,该算法也会导致稍大的参数出现堆栈溢出。