用于缓存和性能的批处理操作,同时避免脏工作

时间:2014-03-11 16:48:36

标签: caching haskell optimization batch-processing memoization

假设我有两个纯但不安全的函数,它们都是相同的,但是其中一个函数正在批处理,并且渐近更快:

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,因此如果以getUntil的间隔调用switch,性能将是最佳的。但是一旦间隔变大,这种实现就会很慢。

在一个命令式程序中,我想我会创建一个TreeMap(可以快速检查间隙)来缓存所有调用。在缓存未命中时,如果间隔长度大于g,则会填充switch的结果,否则将填充f

如何在Haskell中优化它?

我想我只是在寻找:

  • 使用填充函数按需填充的有序地图,如果缺少的范围很小,则使用一个函数填充所有值,直到请求的索引,如果它很大,则填充另一个函数
  • 在地图上执行get操作,返回所有较低值的列表,直到请求的索引。这将产生类似于上面getUntil的函数。

1 个答案:

答案 0 :(得分: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并使用最快的结果,但那是另一个秀。