在内存中“保存”数据映射

时间:2014-04-05 21:09:43

标签: multithreading haskell memory-management haskell-snap-framework

我有三个这样定义的数据结构,其中SLLMObject代表SetListLikeMapByteString分别为:

nouns :: IO [Object]
nouns = liftM LL.words $ B.readFile "nounlist.txt"

obj :: IO ObjectSet
obj =  liftM S.fromList nouns

actions :: IO ActionMap
actions = do
  n <- nouns
  let l = foldl' (\z x -> (x,Sell):(x,Create):z) [] n
  return $ M.fromList $
    (\(x,y) -> ((x, Verb y []), Out (Verb y []) x)) <$> l

现在我有一个函数将未评估的Set和Map绑定到变量ao。一旦进入query,就会通过用户输入接受无限循环的查询并进行处理。通过查找生成适当的响应。

process :: IO ()
process = do
  a <- actions
  o <- obj
  forever $ query "" a o

请记住,我的Map由300,000多个键值对组成:在我的计算机上调用第一个查询时,第一次评估的初始时间开销在大约3-5秒之间;这很好,完全可以预料到。其他所有后续电话都是快速响应的,就像我想要的那样。但是,这只是因为我将此代码作为独立的可执行文件运行,并且可以保留在IO () process内。如果我将这段代码(以及未列出的其余代码)转换成一个库来与...说明一个 Snap Framework Web应用程序,我就不一定会有这种奢侈。基本上我想说的是:如果我要从forever删除process,那么评估的Map和Set肯定会被垃圾收集。实际上,当我从Snap应用程序调用函数时会发生这种情况(我不能保留forever,因为它会阻止Snap应用程序)。来自Snap应用程序的每个后续调用都将具有相同的3-5秒开销,因为它会重新评估相关数据结构。

<小时/> 我的问题:

是否有一种简单的方法可以将Map和Set保存在内存中,以便每次后续查找都很快?我想出的一个想法是运行一个线程来休眠并维护Map和Set的存储。然而,这对我来说似乎有点矫枉过正。我在俯瞰什么?感谢您对我冗长的解释感到满意。

注意:我不一定要寻找代码答案,更多的是建议,建议等。

2 个答案:

答案 0 :(得分:1)

我相信TVar可以做到。

import Control.Concurrent.Concurrent

intial=do
    objects <- newTVarIO Nothing
    --I didn't understand your example code well, bear with me (and fix this.)
    queryMachine <- mkQueryMachine objects
    return queryMachine

queryMachine objects=QueryMachine $ do
    objects''' <- atomically $ do
        objects' <- readTVar objects
        case objects' of
            Nothing -> do
                let objects'' = objectsMaker
                writeTVar objects $ Just objects''
                return objects''
            Just objects'' -> return objects''

    profitFrom objects'''

适应您自己的需要。

<强>解释

TVar是STM monad中的一个可变变量。 STM非常安全。 atomically将其转换为IO操作。对于您正在执行此操作的多种类型的操作,请创建单独的STM操作,并在每个操作上调用atomically。这是因为你希望atomically块很小,所以他们所采取的锁定不会占用过多的锁定#34; (等等。)上述代码可以通过比较来自Control.Concurrent.STM.TSem的TSem来改进,这将确保只有一个线程会尝试计算它,以防两个请求同时进入。

答案 1 :(得分:1)

以下是我对IORef所做的想法:

import Data.IORef
import System.IO.Unsafe 
import Control.Monad 

val_ :: IORef (Maybe Integer)
val_ = unsafePerformIO $ newIORef Nothing

val :: IO Integer
val = do 
  v <- readIORef val_
  case v of 
    Just v' -> return v' 
    Nothing -> do
           v' <- readFile "large.txt" 
           -- replace this part with your actual computation
           let l = sum $ map (fromIntegral . fromEnum) v' 
           writeIORef val_ $ Just l
           return l 

main = do 
  writeFile "large.txt" (replicate (10^7) '0')
  putStrLn "reading"
  replicateM_ 10 (val >>= print)

您确保只评估一次耗时的操作。当您第一次执行val时,它会将值写入IORef并在以后每次从那里检索它。当我跑main时,第一次打印数字需要几秒钟,之后根本没有时间打印。

您需要unsafePerformIO,因为IORef x无法进行垃圾回收,但会IO (IORef x)

请注意,写入IORef不评估任何内容,即使您之前致电val,也会在第一次使用时对其进行评估。

更简单的解决方案可能是使用monad变换器。您没有提供一个示例,说明您的快照程序将在何处使用此表,因此我无法给出一个令人满意的示例。