【问题标题】:Batching actions for caching and performance while avoiding the dirty work缓存和性能的批处理操作,同时避免脏活
【发布时间】: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

switchn 的值,其中gf 便宜。

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


【解决方案1】:

在我刚刚运行的一些测试之后,我将详细说明使用map 的建议。

import System.IO
import System.IO.Unsafe
import Control.Concurrent
import Control.Monad

switch :: Int
switch = 1000

f :: Int -> Int
f x = unsafePerformIO $ do
    threadDelay $ 500 * x
    putStrLn $ "Calculated from scratch: f(" ++ show x ++ ")"
    return $ 500*x

g :: Int -> Int
g x = unsafePerformIO $ do
    threadDelay $ x*x `div` 2
    putStrLn $ "Calculated from scratch: g(" ++ show x ++ ")"
    return $ x*x `div` 2

cachedFG :: [Int]
cachedFG = map g [0 .. switch] ++ map f [switch+1 ..]

main :: IO ()
main = forever $ getLine >>= print . (cachedFG !!) . read

… 其中fgswitch 与问题中的含义相同。

上面的程序可以使用 GHC 编译。执行时,可以输入正整数,后跟换行符,应用程序将根据用户输入的数字打印一些值,以及一些关于从头开始计算的值的额外指示。

这个程序的简短会话是:

User:     10000 
Program:  Calculated from scratch: f(10000)
Program:  5000000
User:     10001
Program:  Calculated from scratch: f(10001)
Program:  5000500
User:     10000
Program:  5000000
^C

程序必须手动终止/终止。

请注意,最后输入的值不会显示“从头开始计算”消息。这表明程序在某处缓存/记忆了值。你可以尝试自己执行这个程序;但要考虑到threadDelay 的滞后与输入的值成正比。

getUntil 函数可以通过以下方式实现:

getUntil :: Int -> [Int]
getUntil n = take n cachedFG

或:

getUntil :: Int -> [Int]
getUntil = flip take cachedFG

如果您不知道switch 的值,可以尝试并行评估fg 并使用最快的结果,但那是另一回事了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-08
    • 1970-01-01
    • 1970-01-01
    • 2018-07-12
    相关资源
    最近更新 更多