GHC’s performance with threads is impressive

Here’s a simple program I wrote on a whim tonight, to take a very basic look at GHC’s low-level threading performance.

module Main where

import Control.Applicative
import Control.Concurrent.MVar
import Control.Concurrent
import Data.Time
import System.Environment

main = do
    mv <- newEmptyMVar
    start <- getCurrentTime
    loop mv =<< read . head <$> getArgs
    end <- getCurrentTime
    putStrLn $ "creation time: " ++ show (diffUTCTime end start)
    putMVar mv 0
    takeMVar mv
    fin <- getCurrentTime
    putStrLn $ "message time: " ++ show (diffUTCTime fin end)

loop :: MVar Int -> Int -> IO ()
loop mv n | n <= 0 = return ()
          | otherwise = do forkIO $ do
                              m <- takeMVar mv
                              putMVar mv $! m+1
                           loop mv (n-1)

This measures two things: how long it takes to fork a given number of threads, and how long it takes to send a tiny message through this ring of threads.

On my 32-bit desktop, I can fork 175,000 threads in one second, and send a message through the ring in 0.09 seconds. This was with a thread stack size of 172 bytes. GHC’s runtime will grow a thread’s stack dynamically if it overflows, so it’s safe to use such a small stack size.

During these runs, the memory consumed by the process maxed out at a slender 45MB.

I find all of these numbers to be very impressive, but they don’t grow linearly. Memory consumption stays low if I create 1.75 million threads (just 354MB), but the time to fork them leaps up to 95 seconds. Messaging through the ring also takes a big jump, to 5.6 seconds.

Posted in haskell
6 comments on “GHC’s performance with threads is impressive
  1. augustss says:

    There’s an open bug report (by me) about the non-linear scaling of thread operations.

  2. Lennart, I can’t find that ticket. Do you happen to know what its number is?

  3. Nick Burlett says:

    Great post, thanks for the simple example!

    Note: when reading this post over on Planet Haskell, I noticed that the less-than signs are missing. I checked the rss feed directly here, and they appear not to be escaped, nor are they escaped in the HTML for this page (although they display correctly). I recommend updating your code snippit with the “<” mark.

  4. Ben Moseley says:

    Is there anything in that code which actually ensures that the message will go all the way round? (ie that the measurements will be measuring the correct thing) – ‘takeMVar’ is documented to be FIFO, but isn’t there a danger that the main thread will get its take in before some of the threads?

  5. Nick, thanks for mentioning the formatting problem. I wish WordPress was more predictable.

    Ben, it is indeed possible that the main thread might sneak in and get the MVar before any of the other threads starts running, but that would be visible in the measured output.

  6. Simon Marlow says:

    Lennart’s GHC bug report is here:

    the non-linear growth of thread creation is due to the garbage collector not having a proper write barrier on threads. Hopefully I’ll get around to fixing it before 6.10.

Leave a Reply

Your email address will not be published. Required fields are marked *