Skip to content

Instantly share code, notes, and snippets.

@eborden
Last active September 26, 2017 15:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eborden/6eefd88ef234c4a8540724f8dfa5a170 to your computer and use it in GitHub Desktop.
Save eborden/6eefd88ef234c4a8540724f8dfa5a170 to your computer and use it in GitHub Desktop.
Benchmark various folding averages
module Main (main) where
import qualified Control.Foldl as Fold
import Criterion.Main
import qualified Data.List
import Data.Maybe
import Data.Semigroup
naiveAvg, foldMapAvg, foldlAvg, foldAvg :: [Float] -> Float
naiveAvg xs = Prelude.sum xs / fromIntegral (Prelude.length xs)
foldMapAvg = fromMaybe 0 . getAverage . foldMap averageDatum
foldlAvg = fromMaybe 0 . getAverage . Data.List.foldl' (\acc x -> acc <> averageDatum x) mempty
foldAvg = Fold.fold ((\s l -> s / fromIntegral l) <$> Fold.sum <*> Fold.length)
main :: IO ()
main = defaultMain
[ bgroup "naiveAvg"
[ bench "1000000" $ nf naiveAvg [1..1000000]
]
, bgroup "foldMapAvg"
[ bench "1000000" $ nf foldMapAvg [1..1000000]
]
, bgroup "foldlAvg"
[ bench "1000000" $ nf foldlAvg [1..1000000]
]
, bgroup "foldAvg"
[ bench "1000000" $ nf foldAvg [1..1000000]
]
]
data Average n = Average { length :: !Int, sum :: !n }
getAverage :: (Num n, Fractional n) => Average n -> Maybe n
getAverage (Average l n) =
if l == 0
then Nothing
else Just $ n / fromIntegral l
averageDatum :: n -> Average n
averageDatum = Average 1
instance Num n => Semigroup (Average n) where
Average lx nx <> Average ly ny = Average (lx + ly) (nx + ny)
instance Num n => Monoid (Average n) where
mappend = (<>)
mempty = Average 0 0
benchmarking naiveAvg/1000000
time 8.569 ms (8.523 ms .. 8.606 ms)
1.000 R² (1.000 R² .. 1.000 R²)
mean 8.586 ms (8.565 ms .. 8.625 ms)
std dev 78.99 μs (54.47 μs .. 126.1 μs)
benchmarking foldMapAvg/1000000
time 48.14 ms (44.37 ms .. 50.39 ms)
0.984 R² (0.960 R² .. 0.996 R²)
mean 49.41 ms (47.33 ms .. 52.64 ms)
std dev 4.895 ms (3.159 ms .. 6.827 ms)
variance introduced by outliers: 37% (moderately inflated)
benchmarking foldlAvg/1000000
time 4.331 ms (4.289 ms .. 4.392 ms)
0.999 R² (0.998 R² .. 1.000 R²)
mean 4.324 ms (4.306 ms .. 4.347 ms)
std dev 66.30 μs (46.57 μs .. 90.51 μs)
benchmarking foldAvg/1000000
time 23.81 ms (21.19 ms .. 25.69 ms)
0.958 R² (0.868 R² .. 0.997 R²)
mean 24.46 ms (23.12 ms .. 25.22 ms)
std dev 2.182 ms (1.161 ms .. 3.652 ms)
variance introduced by outliers: 35% (moderately inflated)
@eborden
Copy link
Author

eborden commented Sep 26, 2017

fold-average

@eborden
Copy link
Author

eborden commented Sep 26, 2017

For extra fun if you utilize Average with Fold then you get the exact same performance as the foldl' variant:

foldAvgWithMon = fromMaybe 0 . getAverage . Fold.fold (Average <$> Fold.length <*> Fold.sum)
benchmarking foldAvgWithMon/1000000
time                 4.311 ms   (4.295 ms .. 4.333 ms)
                     1.000 R²   (0.999 R² .. 1.000 R²)
mean                 4.313 ms   (4.296 ms .. 4.334 ms)
std dev              56.77 μs   (36.97 μs .. 71.66 μs)

@eborden
Copy link
Author

eborden commented Sep 26, 2017

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment