假设我有两个纯但不安全的函数,它们都是相同的,但是其中一个函数正在批处理,并且渐近更快:
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
,因此如果以getUntil
的间隔调用switch
,性能将是最佳的。但是一旦间隔变大,这种实现就会很慢。
在一个命令式程序中,我想我会创建一个TreeMap(可以快速检查间隙)来缓存所有调用。在缓存未命中时,如果间隔长度大于g
,则会填充switch
的结果,否则将填充f
。
如何在Haskell中优化它?
我想我只是在寻找:
getUntil
的函数。答案 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
...其中f
,g
和switch
的含义与问题相同。
上述程序可以使用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
的价值,您可以尝试并行评估f
和g
并使用最快的结果,但那是另一个秀。