Chr*_*ner 8 optimization performance caching haskell ghc
我有一个棘手的问题;
所以,我知道GHC将"缓存"(缺乏一个更好的术语)顶级定义,只计算一次,例如:
myList :: [Int]
myList = fmap (*10) [0..10]
Run Code Online (Sandbox Code Playgroud)
即使我myList
在几个地方使用,GHC注意到该值没有参数,因此它可以共享它并且不会"重建"列表.
我想这样做,但计算依赖于类型级上下文; 一个简单的例子是:
dependentList :: forall n. (KnownNat n) => [Nat]
dependentList = [0..natVal (Proxy @n)]
Run Code Online (Sandbox Code Playgroud)
所以有趣的是,没有'单一'可缓存值dependentList
; 但是一旦应用了一种类型,它就减少到一个常数,所以理论上一旦类型检查器运行,GHC就会认识到几个点都依赖于"相同" dependentList
; 例如(使用TypeApplications)
main = do
print (dependentList @5)
print (dependentList @10)
print (dependentList @5)
Run Code Online (Sandbox Code Playgroud)
我的问题是,GHC会认识到它可以共享这两个5
列表吗?或者它是分别计算每一个?从技术上讲,甚至可以在编译时而不是运行时计算这些值,是否有可能让GHC这样做?
我的情况稍微复杂一些,但是应该遵循与示例相同的约束,但是我的类似dependentList
值是计算密集的.
如果能让事情成为可能,我完全不反对使用类型类来做这件事; GHC缓存并重用类型词典吗?也许我可以把它变成类型类词典中的常量来获取缓存?
想法有人吗?或者有人读过我看看这是如何工作的?
我更喜欢这样做,编译器可以解决它而不是使用手动memoization,但我愿意接受:)
谢谢你的时间!
按照@crockeea的建议,我做了一个实验;这里尝试使用带有多态模糊类型变量的顶级常量,并且也是一个只是为了好玩的实际常量,每个常量都包含一个“trace”
\n\ndependant :: forall n . KnownNat n => Natural\ndependant = trace ("eval: " ++ show (natVal (Proxy @n))) (natVal (Proxy @n))\n\nconstantVal :: Natural\nconstantVal = trace "constant val: 1" 1\n\n\nmain :: IO ()\nmain = do\n print (dependant @1)\n print (dependant @1)\n print constantVal\n print constantVal\n
Run Code Online (Sandbox Code Playgroud)\n\n结果很不幸:
\n\n\xce\xbb> main\neval: 1\n1\neval: 1\n1\nconstant val: 1\n1\n1\n
Run Code Online (Sandbox Code Playgroud)\n\n很明显,每次使用它时,它都会重新评估多态常量。
\n\n但是,如果我们将常量写入类型类(仍然使用模糊类型),则每个实例似乎只会解析一次字典值,当您知道 GHC 为相同的类实例传递相同的字典时,这是有意义的。当然,它会为不同的实例重新运行代码:
\n\n\xce\xbb> main\neval: 1\n1\neval: 1\n1\nconstant val: 1\n1\n1\n
Run Code Online (Sandbox Code Playgroud)\n\n结果:
\n\n\xce\xbb> main\ndependant class: 1\n1\n1\ndependant class: 2\n2\n
Run Code Online (Sandbox Code Playgroud)\n\n至于让 GHC 在编译时执行这些操作,看起来您可以使用lift
TemplateHaskell 使用此技术来执行此操作。
不幸的是,您不能在类型类定义中使用它,因为 TH 会抱怨“@n”必须从不同的模块导入(是的 TH),并且在编译时具体未知。您可以在任何使用类型类值的地方执行此操作,但每次提升都会对其进行评估一次,并且您必须在使用它的所有地方进行提升才能获得好处;相当不切实际。
\n