[Haskell-cafe] Performance best practices

Jaro Reinders jaro.reinders at gmail.com
Thu Aug 1 07:58:15 UTC 2019


I think the best way to test the performance is to use a benchmarking
library such as criterion (http://www.serpentine.com/criterion/)

    import Criterion.Main
    import Data.List (inits,tails)

    splits1, splits2, splits3 :: [a] -> [([a],[a])]
    splits1 xs = zip (inits xs) (tails xs)

    splits2 [] = [([],[])]
    splits2 xs@(x:xs') = ([],xs) : [(x:as,bs) | (as,bs) <- splits2 xs']

    splits3 = go id where
      go prefix xs = (prefix [],xs) : case xs of
        x:xs -> go (prefix . (x :)) xs
        [] -> []

    main = defaultMain
      [ bench "splits1" $ nf splits1 input
      , bench "splits2" $ nf splits2 input
      , bench "splits3" $ nf splits3 input
      ]
      where
        input :: [Int]
        input = [1..1000]

It gives the following results on my computer:

    benchmarking splits1
    time                 4.693 ms   (4.625 ms .. 4.752 ms)
                         0.999 R²   (0.998 R² .. 0.999 R²)
    mean                 4.725 ms   (4.694 ms .. 4.758 ms)
    std dev              97.37 μs   (77.95 μs .. 123.3 μs)

    benchmarking splits2
    time                 12.10 ms   (11.96 ms .. 12.25 ms)
                         0.999 R²   (0.999 R² .. 1.000 R²)
    mean                 12.08 ms   (12.03 ms .. 12.14 ms)
    std dev              149.7 μs   (110.0 μs .. 199.1 μs)

    benchmarking splits3
    time                 4.110 ms   (4.025 ms .. 4.187 ms)
                         0.997 R²   (0.996 R² .. 0.999 R²)
    mean                 4.124 ms   (4.081 ms .. 4.177 ms)
    std dev              147.6 μs   (115.6 μs .. 238.5 μs)
    variance introduced by outliers: 17% (moderately inflated)

On 01-08-2019 04:45, Todd Wilson wrote:
> Dear Cafe:
> 
> I'm getting to the point in my Haskell learning curve where I'm
> getting more interested in the fine points of programming for
> performance and would be grateful for some ideas of the tools and
> options available for exploring this. To make this concrete, let me
> give a simple example. Here are two different ways of coding the same
> list-splitting function:
> 
>    import Data.List (inits,tails)
>    splits1, splits2 :: [a] -> [([a],[a])]
>    splits1 xs = zip (inits xs) (tails xs)
>    splits2 [] = [([],[])]
>    splits2 xs@(x:xs') = ([],xs) : [(x:as,bs) | (as,bs) <- splits2 xs']
> 
> For example, splits1 [1,2,3] is
> [([],[1,2,3]),([1],[2,3]),([1,2],[3]),([1,2,3],[])].
> 
> How do these functions compare in their actual time and space usage?
> It seems that, asymptotically, tails is O(n) while inits is O(n^2) in
> both time and space (when fully evaluated), and splits2 is also
> O(n^2), but how would I go about comparing them in actual usage to get
> a more precise comparison? What profiling or run-time statistics could
> I gather to make a decision about which one to use in a situation
> where performance mattered (assuming for the sake of discussion that
> such a situation existed for this simple example)? And, out of
> curiosity, is there an even more efficient way to code this function?
> Thanks,
> 
> Todd Wilson
> _______________________________________________
> Haskell-Cafe mailing list
> To (un)subscribe, modify options or view archives go to:
> http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
> Only members subscribed via the mailman list are allowed to post.
> 


More information about the Haskell-Cafe mailing list