[Haskell-cafe] Faster timeout but is it correct?
Bas van Dijk
v.dijk.bas at gmail.com
Fri Feb 18 02:01:02 CET 2011
On 18 February 2011 01:09, Bas van Dijk <v.dijk.bas at gmail.com> wrote:
> Benchmarks are coming...
Here are some preliminary benchmarks.
I used the latest GHC HEAD (7.1.20110217) build for performance.
Because I wanted to finish the build of ghc before I went to bed I
used a faster machine than my laptop. So the results should not be
compared to my previous results.
PC specs:
CPU: Intel Core 2 Duo 3Ghz. with 6MB cache
OS: An up to date 64bit Ubuntu 10.10
First of all the implementations:
The current:
data Timeout = Timeout Unique
timeout :: Int -> IO a -> IO (Maybe a)
timeout n f
| n < 0 = fmap Just f
| n == 0 = return Nothing
| otherwise = do
pid <- myThreadId
ex <- fmap Timeout newUnique
handleJust (\e -> if e == ex then Just () else Nothing)
(\_ -> return Nothing)
(bracket (forkIO (threadDelay n >> throwTo pid ex))
(killThread)
(\_ -> fmap Just f))
The new:
newtype Timeout = Timeout ThreadId
timeout :: Int -> IO a -> IO (Maybe a)
timeout n f
| n < 0 = fmap Just f
| n == 0 = return Nothing
| otherwise = do
myTid <- myThreadId
uninterruptibleMask $ \restore -> do
tid <- unsafeUnmask $ forkIO $ do
tid <- myThreadId
threadDelay n
throwTo myTid $ Timeout tid
(restore (fmap Just f) >>= \mb -> killThread tid >> return mb)
`catch` \e ->
case fromException e of
Just (Timeout tid') | tid' == tid -> return Nothing
_ -> killThread tid >> throwIO e
The event-manager based:
newtype Timeout = Timeout TimeoutKey
timeout :: Int -> IO a -> IO (Maybe a)
timeout usecs f
| usecs < 0 = fmap Just f
| usecs == 0 = return Nothing
| otherwise = do
myTid <- myThreadId
Just mgr <- readIORef eventManager
mask $ \restore -> do
key <- registerTimeoutWithKey mgr usecs $ \key ->
throwTo myTid $ Timeout key
let unregTimeout = M.unregisterTimeout mgr key
(restore (fmap Just f) >>= \mb -> unregTimeout >> return mb)
`catch` \e ->
case fromException e of
Just (Timeout key') | key' == key -> return Nothing
_ -> unregTimeout >> throwIO e
The benchmarks: (These should really be extended!)
willTimeout = shouldTimeout $ timeout 1 (threadDelay oneSec)
wontTimeout = shouldNotTimeout $ timeout oneSec (return ())
nestedTimeouts = shouldTimeout $ timeout 100000 $
shouldNotTimeout $ timeout (2*oneSec) $
threadDelay oneSec
externalException = do
(tid, wait) <- fork $ timeout oneSec (threadDelay oneSec)
threadDelay 500
throwTo tid MyException
r <- wait
case r of
Left e | Just MyException <- fromException e -> return ()
_ -> error "MyException should have been thrown!"
Results:
The benchmarks were build with -O2 and -threaded and run without RTS
options (So no -N2, I may do that later but AFAIK the RTS will
automatically find the number of cores)
willTimeout/old 24.34945 us 1.0 x
willTimeout/new 26.91964 us 0.9 x (large std dev: 5 us)
willTimeout/event 12.94273 us 1.9 x :-)
wontTimeout/old 16.25766 us 1.0 x
wontTimeout/new 637.8685 ns 25.5 x :-)
wontTimeout/event 1.565311 us 10.4 x :-)
externalException/old 10.28582 ms 1.0 x
externalException/new 9.960918 ms 1.0 x
externalException/event 10.25484 ms 1.0 x
nestedTimeouts/old 108.1759 ms 1.0 x
nestedTimeouts/new 108.4585 ms 1.0 x
nestedTimeouts/event 109.9614 ms 1.0 x
Preliminary conclusions:
I think the most important benchmark is wontTimeout because AFAIK
that's the most common situation. As can be seen, the new
implementation is 25 times faster than the old one. The event-manager
based implementation is 10 times faster than the old one but not quite
as fast as the new one. Although the event-manager based timeout has
to do less work the new one probably exploits parallelism because it
forks a thread to do part of its work.
A nice result is that in my previous efforts I couldn't achieve
speedups in the willTimeout benchmark. Fortunately the event-manager
based implementation is twice as fast as the original.
Further work:
I will brainstorm on this some more and update my patches for base
during the weekend.
Regards,
Bas
More information about the Haskell-Cafe
mailing list