[Haskell-cafe] strange performance of expression evaluators

Alexander Vodomerov alex at sectorb.msk.ru
Sat Jan 13 04:22:41 EST 2007


On Sat, Jan 13, 2007 at 11:44:38AM +1000, Matthew Brecknell wrote:

> So my advice here would be: always try the optimiser before you worry
> too much about strange performance!
Thanks for help!

I've done some more experiments. The following program defines simple
arithmetic expression with indexed variables. I've written four
different ways to evaluate them:
 - eval1 is simple monadic evaluator
 - eval2 is the obvious straight-forward implentation
 - compile1 is attempt to perform compilation
 - compile2 is refined compile1 with sub-expressions explicitely
     separated via "let" binding.

Test evaluates the same expression in 1000000 different environments.
The results are:
  - eval1 - 17.47 sec
  - eval2 - 3.71 sec
  - compile1 - 3.79 sec
  - compile2 - 3.74 sec

This figures are completely mysterious for me.
   1) I expected eval1 and eval2 to perform equally. In fact, eval1 is
       4.7 times slower for eval2.
   2) I expected compile{1,2} to perform much faster then eval{1,2}.
      However, the "compilation" attempt does not give any speed up at
      all.

Can anyone explain these results?

Program was compiled with "ghc -O3 -fexcess-precision" command.

For comparison, I wrote simple straight-forward implementation of
compile2 in C++. It runs approximately 1.04 sec (3.57 better than
Haskell).
 
import Data.Array.Unboxed
import Data.Array.Base
import Control.Monad.Reader
import Data.Int

foldl' f z []     =  z
foldl' f z (x:xs) =  (foldl' f $! f z x) xs

type Value = Int32
data Expr = Const !Value | Var !Int | Add !Expr !Expr | Sub !Expr !Expr | Mul !Expr !Expr
type Env = UArray Int Value

eval1 :: Expr -> Reader Env Value
eval1 e =
    case e of
      Const c -> return c
      Var n -> do { env <- ask; return $ env ! n }
      Add e1 e2 -> do
             v1 <- eval1 e1
             v2 <- eval1 e2
             return $ v1 + v2
      Sub e1 e2 -> do
             v1 <- eval1 e1
             v2 <- eval1 e2
             return $ v1 - v2
      Mul e1 e2 -> do
             v1 <- eval1 e1
             v2 <- eval1 e2
             return $ v1 * v2

eval2 :: Expr -> Env -> Value
eval2 e env =
    case e of
      Const c -> c
      Var n -> env ! n
      Add e1 e2 -> eval2 e1 env + eval2 e2 env
      Sub e1 e2 -> eval2 e1 env - eval2 e2 env
      Mul e1 e2 -> eval2 e1 env * eval2 e2 env

compile1 :: Expr -> (Env -> Value)
compile1 e =
    case e of
      Const c -> \env -> c
      Var n -> \env -> env ! n
      Add e1 e2 -> \env -> (compile1 e1) env + (compile1 e2) env
      Sub e1 e2 -> \env -> (compile1 e1) env - (compile1 e2) env
      Mul e1 e2 -> \env -> (compile1 e1) env * (compile1 e2) env

compile2 :: Expr -> (Env -> Value)
compile2 e =
    case e of
      Const c -> \env -> c
      Var n -> \env -> env ! n
      Add e1 e2 -> let c1 = compile2 e1 in
                   let c2 = compile2 e2 in
                   \env -> c1 env + c2 env
      Sub e1 e2 -> let c1 = compile2 e1 in
                   let c2 = compile2 e2 in
                   \env -> c1 env - c2 env
      Mul e1 e2 -> let c1 = compile2 e1 in
                   let c2 = compile2 e2 in
                   \env -> c1 env * c2 env

test1 :: Expr
test1 = Add (Mul (Add (Var 0) (Var 1)) (Add (Var 0) (Var 1))) (Mul (Sub (Var 0) (Var 1)) (Sub (Var 0) (Var 1)))

test :: Expr
test = Add (Mul test1 test1) (Add test1 test1)

do_test :: (Env -> Value) -> Value
do_test e =
    foldl' (+) 0 (map (\n -> e (array (0, 1) [(0, n), (1, n)])) [0..1000000])

main = do
  putStrLn "testing empty expr"
  let res0 = do_test (\env -> 0)
  putStrLn $ "result is " ++ show res0
  putStrLn "testing compile 1"
  let res1 = do_test (compile1 test)
  putStrLn $ "result is " ++ show res1
  putStrLn "testing compile 2"
  let res2 = do_test (compile2 test)
  putStrLn $ "result is " ++ show res2
  putStrLn "testing eval 1"
  let res3 = do_test (runReader (eval1 test))
  putStrLn $ "result is " ++ show res3
  putStrLn "testing eval 2"
  let res4 = do_test (eval2 test)
  putStrLn $ "result is " ++ show res4
  return ()



More information about the Haskell-Cafe mailing list