Compute as much of a list as possible in a fixed time

后端 未结 3 1654
遥遥无期
遥遥无期 2020-12-28 14:38

I want to write a function that takes a time limit (in seconds) and a list, and computes as many elements of the list as possible within the time limit.

My first att

3条回答
  •  無奈伤痛
    2020-12-28 15:32

    Here's an example I was able to cook up using some of the suggestions above. I've not done huge amounts of testing to ensure work is cut off exactly when the timer runs out, but based on the docs for timeout, this should work for all things not using FFI.

    import Control.Concurrent.STM
    import Control.DeepSeq
    import System.Timeout
    
    type Time = Int
    
    -- | Compute as many items of a list in given timeframe (microseconds)
    --   This is done by running a function that computes (with `force`)
    --   list items and pushed them onto a `TVar [a]`.  When the requested time
    --   expires, ghc will terminate the execution of `forceIntoTVar`, and we'll
    --   return what has been pushed onto the tvar.
    timeLimited :: (NFData a) => Time -> [a] -> IO [a]
    timeLimited t xs = do
        v <- newTVarIO []
        _ <- timeout t (forceIntoTVar xs v)
        readTVarIO v 
    
    -- | Force computed values into given tvar
    forceIntoTVar :: (NFData a) => [a] -> TVar [a] -> IO [()]
    forceIntoTVar xs v = mapM (atomically . modifyTVar v . forceCons) xs
    
    -- | Returns function that does actual computation and cons' to tvar value
    forceCons :: (NFData a) => a -> [a] -> [a]
    forceCons x = (force x:)
    

    Now let's try it on something costly:

    main = do
        xs <- timeLimited 100000 expensiveThing   -- run for 100 milliseconds
        print $ length $ xs  -- how many did we get?
    
    -- | Some high-cost computation
    expensiveThing :: [Integer]
    expensiveThing = sieve [2..]
      where
          sieve (p:xs) = p : sieve [x|x <- xs, x `mod` p > 0]
    

    Compiled and run with time, it seems to work (obviously there is some overhead outside the timed portion, but I'm at roughly 100ms:

    $ time ./timeLimited
    1234
    ./timeLimited  0.10s user 0.01s system 97% cpu 0.112 total
    

    Also, something to note about this approach; since I'm enclosing the entire operation of running the computations and pushing them onto the tvar inside one call to timeout, some time here is likely lost in creating the return structure, though I'm assuming (if your computations are costly) it won't account or much of your overall time.

    Update

    Now that I've had some time to think about it, due to Haskell's laziness, I'm not 100% positive the note above (about time-spent creating the return structure) is correct; either way, let me know if this is not precise enough for what you are trying to accomplish.

提交回复
热议问题