Search code examples
haskellfunctional-programminglazy-evaluation

Haskell Strict MVar with Bang pattern


The following code example takes approx 2 seconds to execute. When the bang pattern in line 14 is removed however, it takes 60s. Can anyone explain what is going on?

I am using strict MVar so whatever is put into the MVar, should be fully evaluated to normal form. I would not expect a Bang pattern before inserting into the MVar to have any noticable effect.

{-# LANGUAGE BangPatterns #-}

import           Control.Concurrent.MVar.Strict
import qualified Data.Text as T
import           Data.Text.Encoding

main :: IO ()
main = do
    mvar <- newMVar T.empty

    let bsArr = map (\i -> encodeUtf8 $ T.pack $ "some strange string " ++ show i) [0 .. 30000 :: Int]
        mvarWriter =
            \lbs ->
                let !decoded = decodeUtf8 lbs
                in  modifyMVar_ mvar (\oldText -> return $ oldText <> decoded)

    mapM_ (\lbs -> mvarWriter lbs) bsArr
    print . T.length =<< readMVar mvar

Solution

  • Your code is roughly equivalent to:

      let !decoded = decodeUtf8 lbs
      oldText <- takeMVar mvar
      let !newText = oldText <> decoded
      putMVar mvar newText
    

    Without bang pattern it is like this:

      oldText <- takeMVar mvar
      let !newText = oldText <> decodeUtf8 lbs
      putMVar mvar newText
    

    Without bang pattern, the computation happens at the latest point possible. That is right before the new value is inserted. However, at that point the MVar is empty: the oldText has already been taken out. During this time no other threads can compute anything. So this means that only a single thread can do actual computation at any given time.

    The bang pattern forces decodeUtf8 lbs to be evaluated before the MVar is taken. So that part of the computation can happen in parallel with other threads. Only the relatively cheap oldText <> decoded computation needs to happen in the critical section.