如何避免元组值递归函数Worker中的提升值以减少堆分配?
如何让GHC为返回元组的函数生成无堆分配的 unlifted 值worker函数?
最小示例代码
import Test.Tasty.Bench {-# INLINE loop #-} loop :: Int -> Int -> Int loop a 0 = a loop a n = loop (a + x + y) (n - 1) where (x, y) = foo n {-# INLINE foo #-} foo :: Int -> (Int, Int) foo n = if n > 0 then (n + 1, n) else foo (n + 1) main :: IO () main = defaultMain [bench "test" $ whnf (loop 0) (1024 * 1024)]
运行结果
test: OK (0.24s) 1.88 ms ± 88 μs, 32 MB allocated, 2.8 KB copied, 2.0 MB peak memory
Core Dump分析
查看Core Dump后,得到以下worker函数:
Rec { -- RHS size: {terms: 18, types: 6, coercions: 0, joins: 0/0} $wfoo = \ ww -> case ># ww 0# of { __DEFAULT -> $wfoo (+# ww 1#); 1# -> (# I# (+# ww 1#), I# ww #) } end Rec } Rec { -- RHS size: {terms: 26, types: 14, coercions: 0, joins: 0/0} $wloop = \ ww ww1 -> case ww1 of ds { __DEFAULT -> case $wfoo ds of { (# ww3, ww4 #) -> case ww3 of { I# y -> case ww4 of { I# y1 -> $wloop (+# (+# ww y) y1) (-# ds 1#) } } }; 0# -> ww } end Rec }
堆分配的根源是$wfoo返回的是lifted Int值,每次调用都会在堆上分配内存。
已尝试的解决方案
- 添加
seq x $ seq y这类严格性注解:对Core Dump无实质影响,仅会重命名绑定变量。 - 改为让
foo返回单个Int:GHC会自动移除所有lifted Int值,程序无堆分配,但不符合生产代码需要返回元组的需求。 - 使用带严格字段的自定义数据类型
data Pair = Pair !Int !Int:可以避免lifted Int值,且Core中会直接使用未装箱元组的unlifted值,但无法用多态严格元组实现,不适用于生产场景。
问题
生产代码必须使用标准元组,因此上述方案均不适用。既然严格字段的数据类型最终会被擦除,本质是一种间接的严格性注解,那是否存在更直接的方式,让GHC为foo生成返回unlifted值的worker函数,从而消除堆分配?
使用环境:GHC 8.10.7、LLVM 13.0.1,编译参数-O2 -fllvm -optlo-O3,运行参数+RTS -T
内容的提问来源于stack exchange,提问作者Jules
相关产品推荐
相关产品推荐

