STM: F# vs Haskell
STM is a very nice parallel programming model used intensively in Haskell and Clojure. There's a F# implementation which can be found in FSharpx library.
It took about 1,6 seconds which is an order of magnitude slower than the Haskell result. It's rather frustrating.
Today I'm going to test performance of both the Haskell and the F# STMs. The test is very simple - read a couple TVars, check their equality, then write them back incremented by 1, repeat a million times.
First, the Haskell code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Control.Concurrent.STM | |
import Control.Monad (forM_) | |
import System.Environment (getArgs) | |
main :: IO () | |
main = do | |
nStr:_ <- getArgs | |
let n = read nStr :: Int | |
x <- atomically $ newTVar (0::Int) | |
y <- atomically $ newTVar (0::Int) | |
forM_ [1..n] $ atomically . \_ -> do | |
x' <- readTVar x | |
y' <- readTVar y | |
check (x' == y') | |
writeTVar x (x' + 1) | |
writeTVar y (y' + 1) | |
print "Done." | |
{- | |
STMTest 1000000 +RTS -s | |
"Done." | |
16,054,596 bytes allocated in the heap | |
5,264 bytes copied during GC | |
41,860 bytes maximum residency (2 sample(s)) | |
23,676 bytes maximum slop | |
1 MB total memory in use (0 MB lost due to fragmentation) | |
Tot time (elapsed) Avg pause Max pause | |
Gen 0 29 colls, 0 par 0.00s 0.00s 0.0000s 0.0000s | |
Gen 1 2 colls, 0 par 0.00s 0.00s 0.0001s 0.0001s | |
INIT time 0.00s ( 0.00s elapsed) | |
MUT time 0.17s ( 0.17s elapsed) | |
GC time 0.00s ( 0.00s elapsed) | |
EXIT time 0.00s ( 0.00s elapsed) | |
Total time 0.17s ( 0.17s elapsed) | |
%GC time 0.0% (0.3% elapsed) | |
Alloc rate 93,557,652 bytes per MUT second | |
Productivity 100.0% of total user, 100.9% of total elapsed -} |
So, it took about 170 ms. OK, now F#:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open FSharpx.Stm | |
open FSharpx.TimeMeasurement | |
[<EntryPoint>] | |
let main argv = | |
let n = int (argv.[0]) | |
let x = newTVar 0 | |
let y = newTVar 0 | |
// FSharpx's STM does not define the Haskell's check function, so we define our own here | |
let check (b: bool) = stm { if b then return () else return! retry() } | |
stopTime <| fun() -> | |
seq { 1..n } | |
|> mapM_ (fun i -> | |
stm { | |
let! x' = readTVar x | |
let! y' = readTVar y | |
do! check (x' = y') | |
do! writeTVar x (x' + 1) | |
do! writeTVar y (y' + 1) }) | |
|> atomically | |
|> snd | |
|> printfn "Elapsed: %A ms" | |
0 | |
(* | |
> FSharpSTMTest.exe 1000000 | |
Elapsed: 1572.0 ms | |
*) |
Comments