8c6794b6.github.io

Guard, case, and pattern match

I tend to use case with tuples when a function takes more than one arguments. Alternate to tupling, we can write with guards or, with direct pattern match in function arguments.

What differences we can find in compiled result?

> {-# LANGUAGE BangPatterns #-}
> module GuardCasePatmatch where
>
> import Criterion.Main
> import Data.Int

Target function is, naive power function. First, with guard:

> pow_guard :: Int64 -> Int64 -> Int64
> pow_guard !a !b
>   | b == 0    = 1
>   | otherwise = a * pow_guard a (b-1)

Next, tupled case:

> pow_case :: Int64 -> Int64 -> Int64
> pow_case !a !b = case (a,b) of
>   (_,0) -> 1
>   _     -> a * pow_case a (b-1)

Last, pattern match

> pow_patmatch :: Int64 -> Int64 -> Int64
> pow_patmatch !a 0 = 1
> pow_patmatch !a !b = a * pow_patmatch a (b-1)

Testing that all three give same result:

> test :: Bool
> test = all id [f x y | x <- [1..32], y <- [1..32]] where
>   f a b =
>     let pc = pow_case a b
>         pg = pow_guard a b
>         pp = pow_patmatch a b
>     in  pc == pg && pg == pp

Result:

ghci> test
True

Seems fine. Now benchmarking:

> main :: IO ()
> main = defaultMain
>  [ bench "guard" (nf (pow_guard 2) 64)
>  , bench "case" (nf (pow_case 2) 64)
>  , bench "patmatch" (nf (pow_patmatch 2) 64)
>  ]

Compile and run:

$ ghc --numeric-version
7.4.1
$ ghc -fllvm -optl-O3 -O3 -o a.out -main-is GuardCasePatmatch gcp.lhs

Result:

benchmarking guard
mean: 386.1059 ns, lb 385.6170 ns, ub 386.6460 ns, ci 0.950
std dev: 2.640960 ns, lb 2.261563 ns, ub 3.111325 ns, ci 0.950

benchmarking case
mean: 382.4345 ns, lb 382.0820 ns, ub 382.8864 ns, ci 0.950
std dev: 2.030602 ns, lb 1.667205 ns, ub 2.567343 ns, ci 0.950

benchmarking patmatch
mean: 385.1643 ns, lb 384.6979 ns, ub 385.7441 ns, ci 0.950
std dev: 2.657997 ns, lb 2.163594 ns, ub 3.363755 ns, ci 0.950

Almost same. How about dumped core?

Exerpt of dumped core of power_guard looks like this:

Rec {
GuardCasePatmatch.$wpow_guard [Occ=LoopBreaker]
  :: GHC.Prim.Int# -> GHC.Prim.Int# -> GHC.Prim.Int#
[GblId, Arity=2, Caf=NoCafRefs, Str=DmdType LL]
GuardCasePatmatch.$wpow_guard =
  \ (ww_s2qq :: GHC.Prim.Int#) (ww1_s2qu :: GHC.Prim.Int#) ->
    case ww1_s2qu of wild_X1r {
      __DEFAULT ->
        case GuardCasePatmatch.$wpow_guard ww_s2qq (GHC.Prim.-# wild_X1r 1)
        of ww2_s2qy { __DEFAULT ->
        GHC.Prim.*# ww_s2qq ww2_s2qy
        };
      0 -> 1
    }
end Rec }

...

GuardCasePatmatch.pow_guard =
  \ (w_s2qo :: GHC.Int.Int64) (w1_s2qs :: GHC.Int.Int64) ->
    case w_s2qo of _ { GHC.Int.I64# ww_s2qq ->
    case w1_s2qs of _ { GHC.Int.I64# ww1_s2qu ->
    case GuardCasePatmatch.$wpow_guard ww_s2qq ww1_s2qu
    of ww2_s2qy { __DEFAULT ->
    GHC.Int.I64# ww2_s2qy
    }
    }
    }

Dumped core of power_case:

Rec {
GuardCasePatmatch.$wpow_case [Occ=LoopBreaker]
  :: GHC.Prim.Int# -> GHC.Prim.Int# -> GHC.Prim.Int#
[GblId, Arity=2, Caf=NoCafRefs, Str=DmdType LL]
GuardCasePatmatch.$wpow_case =
  \ (ww_s2qc :: GHC.Prim.Int#) (ww1_s2qg :: GHC.Prim.Int#) ->
    case ww1_s2qg of wild_X1n {
      __DEFAULT ->
        case GuardCasePatmatch.$wpow_case ww_s2qc (GHC.Prim.-# wild_X1n 1)
        of ww2_s2qk { __DEFAULT ->
        GHC.Prim.*# ww_s2qc ww2_s2qk
        };
      0 -> 1
    }
end Rec }

...

GuardCasePatmatch.pow_case =
  \ (w_s2qa :: GHC.Int.Int64) (w1_s2qe :: GHC.Int.Int64) ->
    case w_s2qa of _ { GHC.Int.I64# ww_s2qc ->
    case w1_s2qe of _ { GHC.Int.I64# ww1_s2qg ->
    case GuardCasePatmatch.$wpow_case ww_s2qc ww1_s2qg
    of ww2_s2qk { __DEFAULT ->
    GHC.Int.I64# ww2_s2qk
    }
    }
    }

And dumped core of pow_patmatch:

Rec {
GuardCasePatmatch.$wpow_patmatch [Occ=LoopBreaker]
  :: GHC.Prim.Int# -> GHC.Prim.Int# -> GHC.Prim.Int#
[GblId, Arity=2, Caf=NoCafRefs, Str=DmdType LL]
GuardCasePatmatch.$wpow_patmatch =
  \ (ww_s2qE :: GHC.Prim.Int#) (ww1_s2qI :: GHC.Prim.Int#) ->
    case ww1_s2qI of wild_X1t {
      __DEFAULT ->
        case GuardCasePatmatch.$wpow_patmatch
               ww_s2qE (GHC.Prim.-# wild_X1t 1)
        of ww2_s2qM { __DEFAULT ->
        GHC.Prim.*# ww_s2qE ww2_s2qM
        };
      0 -> 1
    }
end Rec }

...

GuardCasePatmatch.pow_patmatch =
  \ (w_s2qC :: GHC.Int.Int64) (w1_s2qG :: GHC.Int.Int64) ->
    case w_s2qC of _ { GHC.Int.I64# ww_s2qE ->
    case w1_s2qG of _ { GHC.Int.I64# ww1_s2qI ->
    case GuardCasePatmatch.$wpow_patmatch ww_s2qE ww1_s2qI
    of ww2_s2qM { __DEFAULT ->
    GHC.Int.I64# ww2_s2qM
    }
    }
    }

No difference other than bounded names.

TAGGED: haskell, performance, ghc