有没有办法告诉 Haskell 运行时对具有大致相同输入的函数使用缓存?

问题描述 投票:0回答:1

我有一个 Haskell 函数,它将几个浮点数作为输入。这个函数的计算成本有点高,所以如果我能告诉 Haskell,“如果所有输入都在 +/- 1 ppm 以内,请使用缓存函数,那就太好了。”

有没有办法做到这一点?

我想我可以写一个包装器,对所有输入做这样的事情:

1E-6 * floor(x * 1E6)

这增加了 Haskell 将两个几乎相同的浮点数视为相同数字的可能性,但是当算法依赖于两个浮点数的完美等价时,您有时会遇到问题。我总是尽量避免这种情况。

所以在我这样做之前,有没有办法告诉 GHC 的缓存机制容忍预定数量的溢出?

haskell caching ghc
1个回答
0
投票

扩展 Daniel 的评论,GHC 根本没有内置任何类型的缓存或记忆。

这个很容易看出来:

-- foo.hs
import Debug.Trace (trace)

f :: () -> Char
f () = trace "f called" '7'

main :: IO ()
main = do
  print $ f ()
  print $ f ()
  print $ f ()

现在如果你

runhaskell foo.hs
,你应该看到这个:

f called
'7'
f called
'7'
f called
'7'

尽管

f
的输入类型
()
实际上只有一个可能的非底值,但 GHC 没有缓存
f ()
作为返回值
7
;它评估了调用
f ()
三次,每次触发
trace
1

人们有时会说惰性求值涉及某种记忆,但老实说我不知道为什么。当然,我们可以这样做:

-- bar.hs
import Debug.Trace (trace)

f :: () -> Char
f () = trace "f called" '7'

main :: IO ()
main = do
  putStrLn "before r is bound"
  let r = f ()
  putStrLn "after r is bound"
  print r
  print r
  print r

runhaskell bar.hs
会打印这个:

❯ runhaskell bar.hs
before r is defined
after r is defined
f called
'7'
'7'
'7'

在这里我们可以看到

r
只被评估了一次,评估并没有发生在
main
中定义
r = f ()
的那一点,而是在
r
第一次used
print r中发生
.因此,GHC 似乎在其 3 种用法中“缓存”了
r
的值。

但这完全不足为奇!如果我们在几乎 any 主流编程语言中绑定一个变量,然后多次使用该变量,我们期望绑定到该变量的表达式最多被计算一次。这不是“缓存”,它只是普通的变量绑定。通常它是绑定变量的全部point。 Haskell 中的惰性意味着可以在不实际计算 RHS 的情况下绑定变量;系统可以等着看什么时候(如果有的话)第一次使用该变量。但这与记忆无关。在任何情况下,函数调用都不会首先检查其参数以查看是否已经对该参数评估了先前的调用。


1 如果您不知道,

Debug.Trace
模块具有多种功能,旨在帮助进行“打印调试”,而无需重写您尝试调试的所有代码以进入
IO
,通常需要打印到控制台。

trace
只是将它的第一个参数打印到控制台,然后返回它的第二个参数,但它的类型取决于编译器,因此它 看起来 像一个纯函数。因此,在
trace
中进行
f
调用可以让我们看到运行时系统何时评估调用。因为编译器不知道
trace
打印到控制台的副作用,所以这不会阻止它缓存对
f
的调用或应用任何其他优化来更改
f
被调用的次数。


现在,回到

foo.hs
GHC 实际上不能保证打印
f called
3 次。如果我们优化编译
foo.hs
然后运行它,我们会看到一些不同的东西:

❯ ghc -O foo.hs
[1 of 1] Compiling Main             ( foo.hs, foo.o )
Linking foo ...

❯ ./foo
f called
'7'
'7'
'7'

现在

f ()
只评价过一次!但是这个 still 不是缓存或记忆。这只是一个编译时优化; GHC 注意到同一个表达式在源代码中出现了多次,它认为它们是纯表达式(因为
trace
谎报了它的类型),所以它将代码转换为只对表达式求值一次的代码。这只是一个公共子表达式消除优化,与优化 C 编译器可能完成的相同。它与记忆无关;在运行时没有任何东西“记住”调用的结果并检查以后的调用以查看它们是否具有相同的参数。它完全基于源代码中出现的相同表达式(而不是在运行时)。


所以,在 GHC 中根本没有内置记忆或缓存机制的情况下,显然没有什么可以调整来模糊地记忆浮点参数。如果你想在 Haskell 中进行记忆,你需要编写代码来实现它,或者使用 Hackage 上有很多的记忆库。我不知道它们中的任何一个是否允许您提供自定义函数来决定调用何时与先前记录的调用匹配(这是您自定义浮点函数以使用近似相等性所需要的)。

© www.soinside.com 2019 - 2024. All rights reserved.