【发布时间】:2019-12-28 23:32:14
【问题描述】:
我有以下类型和两个相关函数,我打算将它们作为大型列表折叠的一部分进行测量:
类型和访问函数:
data Aggregate a = Aggregate (Maybe a) (a -> Aggregate a)
get :: Aggregate a -> Maybe a
get (Aggregate get' _) = get'
put :: Aggregate a -> a -> Aggregate a
put (Aggregate _ put') = put'
第一个函数:
updateFirst :: Maybe a -> a -> Aggregate a
updateFirst cur val = Aggregate new (updateFirst new)
where new = mplus cur (Just val)
first :: Aggregate a
first = Aggregate Nothing (updateFirst Nothing)
第二个功能:
updateMinimum :: Ord a => Maybe a -> a -> Aggregate a
updateMinimum cur val = Aggregate new (updateMinimum new)
where new = min <$> (mplus cur (Just val)) <*> Just val
minimum :: Ord a => Aggregate a
minimum = Aggregate Nothing (updateMinimum Nothing)
函数的编写方式应使内存保持不变。因此,我选择在 GHC 中使用 Strict 语言扩展,这会强制评估 thunk。 Weigh 库用于执行分配测量:
test :: A.Aggregate Double -> Int -> Maybe Double
test agg len = A.get $ F.foldl' A.put agg (take len $ iterate (+0.3) 2.0)
testGroup :: String -> A.Aggregate Double -> Weigh ()
testGroup name agg = sequence_ $ map (\cnt -> func (str cnt) (test agg) cnt) counts
where
counts = map (10^) [0 .. 6]
str cnt = name ++ (show cnt)
main :: IO ()
main =
mainWith
(do setColumns [Case, Allocated, Max, GCs]
testGroup "fst" A.first
testGroup "min" A.minimum
)
Weigh 输出如下:
Case Allocated Max GCs
fst1 304 96 0
fst10 2,248 96 0
fst100 21,688 96 0
fst1000 216,088 96 0
fst10000 2,160,088 96 2
fst100000 21,600,088 96 20
fst1000000 216,000,088 96 207
min1 552 96 0
min10 4,728 96 0
min100 46,488 96 0
min1000 464,088 96 0
min10000 4,967,768 96 4
min100000 49,709,656 6,537,712 44
min1000000 497,226,840 103,345,512 445
为什么 GHC 突然在大小为 10^5 和 10^6 的输入中分配更多内存?我的 GHC 版本是8.4.4。
【问题讨论】:
-
这里的嫌疑人是
new = ...,由于懒惰,直到需要时才对其进行评估。在返回结果之前尝试强制执行,如updateFirst cur val = new `seq` Aggregate new (updateFirst new)。或者,爆炸模式也可以工作。 (也许需要更多的强迫,但我会从这个开始。) -
包含
updateFirst的模块正在使用Strict扩展名-我的理解是它会自动强制所有thunk-不是这样吗? -
可能是这种情况,如果这也是定义
Aggregate的模块。不过,我会尝试测试它。 -
@DanielLovasko “我的理解是它会自动强制所有 thunk”
Strict扩展比这更卑微。这相当于使模块中定义的所有数据类型的字段严格。严格的字段意味着,每当数据类型的值被评估为 WHNF(例如,作为foldl'的累加器)时,该字段也将被评估为 WHNF。请注意,特别是Maybe不会变得严格,因为它在其他地方定义。也许这就是问题的一部分。尝试强制Just内部的值,而不仅仅是外部Maybe。 -
除了铅笔和纸之外,是否有任何工具可以帮助我找到导致 thunk 创建的表达式?或者更确切地说,GHC 有没有办法打印它创建的所有 thunk?至于
Strict,我认为您描述的是StrictData,这使得类型严格,但纯Strict扩展也应该达到函数参数。
标签: haskell memory-management ghc thunk