【发布时间】:2023-03-06 06:15:01
【问题描述】:
假设我有两个纯但不安全的函数,它们的作用相同,但其中一个是批量处理的,并且渐近更快:
f :: Int -> Result -- takes O(1) time
f = unsafePerformIO ...
g :: [Int] -> [Result] -- takes O(log n) time
g = unsafePerformIO ...
一个简单的实现:
getUntil :: Int -> [Result]
getUntil 0 = f 0
getUntil n = f n : getUntil n-1
switch 是n 的值,其中g 比f 便宜。
getUntil 实际上会随着n 的增加而被调用,但它可能不会从0 开始。因此,由于 Haskell 运行时可以记忆 getUntil,如果以低于 switch 的间隔调用 getUntil,性能将是最佳的。但是一旦间隔变大,这个实现就会很慢。
在命令式程序中,我想我会制作一个 TreeMap(可以快速检查是否存在间隙)来缓存所有调用。在缓存未命中时,如果间隙的长度大于switch,它将填充g 的结果,否则将填充f。
如何在 Haskell 中对此进行优化?
我想我只是在寻找:
- 使用填充函数按需填充的有序映射,如果缺失范围小,则使用一个函数填充所有值直至请求的索引,如果缺失范围大,则使用另一个函数
- 地图上的获取操作,它返回所有较低值的列表,直到请求的索引。这将产生类似于上述
getUntil的函数。
【问题讨论】:
-
fOrG xs = if length xs < switch then map f xs else g xs?或者也许我还不明白这个问题。 -
“既然 Haskell 运行时可以记忆
getUntil...” 我并不完全清楚你在问什么,但请注意 Haskell 运行时 不会 记住getUntil之类的函数,除非您专门以能够启用记忆的方式编写它。 -
@kosmikus,好吧,好吧,如果 getUntil 不会被记忆,那么这个版本的优化甚至不如我想象的那么好。但如果我们想象它被记忆了,它仍然不会有效,原因如下。
-
@DanielWagner,问题是我不知道如何制定“getUntil”,以便它返回所有先前 n 的 f 或 g 列表。这里的函数不知道哪些 n 被缓存,哪些没有被缓存(如果你想象 getUntil 被记忆,它似乎不是)。
-
在这种特殊情况下,您不能使用
getUntil = map f [0 ..]吗?不需要存储缓存等。需要时,只需使用take n getUntil。
标签: caching haskell optimization batch-processing memoization