Advertisement
Chatanga

Haskell timeout with partial results based on CPU time

Nov 7th, 2015
219
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. {-# LANGUAGE BangPatterns #-}
  2.  
  3. import Control.Concurrent
  4. import Control.Monad
  5. import Control.Exception
  6. import System.CPUTime
  7. import System.IO
  8. import System.Mem
  9.  
  10.  
  11. main :: IO ()
  12. main = do
  13.     hSetBuffering stdout NoBuffering
  14.     hSetBuffering stderr NoBuffering
  15.     loop 0
  16.  
  17. loop :: Int -> IO ()
  18. loop n = do
  19.     calculateAsMuchOfPiAsPossible
  20.     when (n < 100) $ loop (n + 1)
  21.  
  22. calculateAsMuchOfPiAsPossible :: IO ()
  23. calculateAsMuchOfPiAsPossible = do
  24.     startCpu <- getCPUTime
  25.  
  26.     let timeLimitInMs = 40
  27.  
  28.     r <- runWithCpuTimeLimit timeLimitInMs $ \result -> do
  29.         putMVar result []
  30.         calculatePi (1, 0, 1, 1, 3, 3) result
  31.  
  32.     -- {--
  33.     let showResult r = head digits : '.' : tail digits
  34.             where digits = map (head . show) (reverse r)
  35.  
  36.     hPutStrLn stdout (showResult r)
  37.     -- --}
  38.  
  39.     endCpu <- getCPUTime
  40.     let elapsedCpuTime = (endCpu - startCpu) `div` (1000 * 1000 * 1000)
  41.  
  42.     when (elapsedCpuTime > timeLimitInMs) $ hPutStrLn stderr (show elapsedCpuTime ++ " ms")
  43.  
  44. -- Adapted from http://rosettacode.org/wiki/Pi#Haskell
  45. calculatePi :: (Integer, Integer, Integer, Integer, Integer, Integer) -> MVar [Integer] -> IO ()
  46. calculatePi (!q, !r, !t, !k, !n, !l) result = do
  47.     {--
  48.     Force rescheduling and faster timeout detection (if not,
  49.     the default rescheduling timer has a 20 ms granularity).
  50.     --}
  51.     yield
  52.     if 4*q+r-t < n*t
  53.         then do
  54.             ns <- takeMVar result
  55.             putMVar result (n : ns)
  56.             calculatePi (10*q, 10*(r-n*t), t, k, div (10*(3*q+r)) t - 10*n, l) result
  57.         else
  58.             calculatePi (q*k, (2*q+r)*l, t*l, k+1, div (q*(7*k+2)+r*l) (t*l), l+2) result
  59.  
  60. runWithCpuTimeLimit :: Integer -> (MVar a -> IO ()) -> IO a
  61. runWithCpuTimeLimit delayInMs action = do
  62.  
  63.     result <- newEmptyMVar
  64.     threadId <- forkIO $ action result
  65.  
  66.     done <- newEmptyMVar
  67.     forkIO $ (getCPUTime >>= sleepUntilDelayElapsed delayInMs) `finally` (putMVar done ())
  68.     takeMVar done
  69.  
  70.     r <- takeMVar result
  71.     killThread threadId
  72.  
  73.     return r
  74.  
  75. sleepUntilDelayElapsed :: Integer -> Integer -> IO ()
  76. sleepUntilDelayElapsed delayInMs t0 = do
  77.     let
  78.         sleepDuration = 1000 * 1000 * 1000 -- 1 ms
  79.         picoToMicro x = x `div` (1000 * 1000)
  80.         milliToPico x = x * 1000 * 1000 * 1000
  81.     t <- getCPUTime
  82.     when (t - t0 + sleepDuration < milliToPico delayInMs) $ do
  83.         {--
  84.         There is no guarantee that the thread will be rescheduled promptly
  85.         when the delay has expired, but the thread will never continue to
  86.         run earlier than specified.
  87.  
  88.         in http://hackage.haskell.org/package/base-4.8.1.0/docs/Control-Concurrent.html
  89.  
  90.         Could be a problem if we are rescheduled too late.
  91.         --}
  92.         threadDelay (fromIntegral $ picoToMicro sleepDuration)
  93.         -- performGC
  94.         sleepUntilDelayElapsed delayInMs t0
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement