Haskell在数据结构上的常量传播?

时间:2013-09-13 01:56:02

标签: arrays haskell

我想知道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编译时相同的结果。

1 个答案:

答案 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`` 10k ``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)

这个很好,因为我们急切地计算了这对。