我想知道Haskell在编译时对数据结构的评估有多深。
考虑以下列表:
simpleTableMultsList :: [Int]
simpleTableMultsList = [n*m | n <- [1 ..9],m <- [1 ..9]]
此列表给出了1到9的乘法表的表示。现在,假设我们要更改它,以便我们将两个一位数字的乘积表示为一对数字(第一个数字,第二个数字)。然后我们可以考虑
simpleTableMultsList :: [(Int,Int)]
simpleTableMultsList = [(k `div` 10, k `rem` 10) | n <- [1 ..9],m <- [1 ..9],let k = n*m]
现在我们可以在一位数字上实现乘法作为表查找。好极了!!但是,我们希望比这更有效率!所以我们想让这个结构成为一个未装箱的数组。 Haskell使用
提供了一个非常好的方法import qualified Data.Array.Unboxed as A
然后我们可以这样做:
simpleTableMults :: A.Array (Int,Int) (Int,Int)
simpleTableMults = A.listArray ((1,1),(9,9)) simpleTableMultsList
现在,如果我想要两个一位数n和m的恒定时间乘法,我可以这样做:
simpleTableMults ! (n,m)
太好了!现在假设我编译了我们一直在努力的这个模块。是否完全评估了simpleTableMults,以便在运行计算时使用simpleTableMults! (n,m),程序字面上在内存中进行查找......或者它是否必须首先在内存中构建数据结构。由于它是一个未装箱的数组,我的理解是必须立即创建数组并且其元素完全严格 - 以便完全评估数组的所有元素。
所以我的问题是:这个评估何时发生,我可以强制它在编译时发生吗?
-------编辑---------------
我试图进一步挖掘这个!我尝试编译和检查有关核心的信息。似乎GHC在编译时对代码进行了大量的减少。我希望我能更多地了解核心能够分辨。如果我们用
编译ghc -O2 -ddump-simpl-stats Main.hs
我们可以看到执行了98次beta缩减,执行了解包列表操作,展开了许多内容,并执行了大量内联(大约150个)。它甚至可以告诉你beta减少的位置,...自从IxArray这个词出现以来,如果发生某种简化,我会更好奇。从我的观点来看,现在有趣的是添加
simpleTableMults = D.deepseq t t
where t = A.listArray ((1,1),(9,9)) simpleTableMultsList
在编译时大大增加了beta减少,内联和简化的数量。如果我可以将编译加载到某种类型的调试器并“查看”数据结构,那将是非常好的!就目前而言,我比之前更加错误。
------编辑2 -------------
我仍然不知道正在执行哪些beta减少。然而,我确实根据sassa-nf的回复反应找到了一些有趣的东西。对于以下实验,我使用了ghc-heap-view包。根据Sassa-NF的回答,我改变了Array在源代码中的表示方式。我将程序加载到GHCi中,并立即调用
:printHeap simpleTableMults
并且正如预期的那样得到一个索引太大的异常。但是在建议的解压缩数据类型下,我得到了一个带有toArray和一堆_thunks以及一些_funs的let表达式。还不确定这些是什么意思......另一件有趣的事情是,通过在源代码中使用seq或其他严格强制,我最终得到了let中的所有_thunks。如果有帮助,我可以上传准确的发射。
此外,如果我执行单个索引,则在所有情况下都会完全评估该数组。
此外,无法通过优化调用ghci,因此我可能无法获得与使用GHC -O2编译时相同的结果。
答案 0 :(得分:2)
让我们夸张:
import qualified Data.Array.Unboxed as A
simpleTableMults :: A.Array (Int,Int) (Int,Int)
simpleTableMults = A.listArray ((1,1),(10000,2000))
[(k `div` 10, k `rem` 10) | n <- [1 ..10000],m <- [1 ..2000],let k = n*m]
main = print $ simpleTableMults A.! (10000,1000)
然后
ghc -O2 -prof b.hs
b +RTS -hy
......Out of memory
hp2hs b.exe.hp
发生什么事了?!您可以看到堆消耗图表超过1GB,然后就死了。
嗯,这对是热切计算的,但是对的预测是懒惰的,所以我们最终得到了大量的thunks来计算k ``div`` 10
和k ``rem`` 10
。
import qualified Data.Array.Unboxed as A
data P = P {-# UNPACK #-} !Int {-# UNPACK #-} !Int deriving (Show)
simpleTableMults :: A.Array (Int,Int) P
simpleTableMults = A.listArray ((1,1),(10000,2000))
[P (k `div` 10) (k `rem` 10) |
n <- [1 ..10000],m <- [1 ..2000],let k = n*m]
main = print $ simpleTableMults A.! (10000,1000)
这个很好,因为我们急切地计算了这对。