├── .gitignore ├── README.md ├── Report.hs ├── Time.hs ├── bench.cabal └── stack.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # numbers 2 | 3 | Benchmarks for numbers: ints, doubles, bignums, rationals, etc. 4 | 5 | ## Running 6 | 7 | For all benchmarks: 8 | 9 | $ stack bench :time 10 | 11 | 12 | 13 | ## Addition 14 | 15 | |Name|1|10|100|1000|10000|100000|1000000| 16 | |---|---|---|---|---|---|---|---| 17 | |Int|7.588 ns|20.33 ns|0.082 μs|0.598 μs|5.759 μs|0.058 ms|0.577 ms| 18 | |Double|7.058 ns|14.15 ns|0.129 μs|1.161 μs|11.60 μs|0.115 ms|1.156 ms| 19 | |Integer|26.80 ns|160.8 ns|1.424 μs|14.20 μs|140.2 μs|1.410 ms|14.23 ms| 20 | |Rational|114.5 ns|920.7 ns|8.981 μs|89.18 μs|892.8 μs|8.950 ms|89.49 ms| 21 | |Scientific|265.6 ns|1608 ns|15.05 μs|148.1 μs|1479 μs|14.95 ms|147.6 ms| 22 | 23 | ## Subtraction 24 | 25 | |Name|1|10|100|1000|10000|100000|1000000| 26 | |---|---|---|---|---|---|---|---| 27 | |Int|6.909 ns|19.52 ns|0.082 μs|0.603 μs|5.753 μs|0.058 ms|0.574 ms| 28 | |Double|6.506 ns|13.93 ns|0.128 μs|1.160 μs|11.50 μs|0.116 ms|1.151 ms| 29 | |Integer|29.91 ns|193.6 ns|1.746 μs|17.50 μs|173.8 μs|1.753 ms|17.47 ms| 30 | |Rational|106.0 ns|937.7 ns|9.099 μs|91.50 μs|920.2 μs|9.099 ms|90.76 ms| 31 | |Scientific|246.2 ns|1571 ns|14.22 μs|140.5 μs|1399 μs|14.05 ms|141.1 ms| 32 | 33 | ## Integer division 34 | 35 | |Name|1000|10000|100000|1000000|10000000| 36 | |---|---|---|---|---|---| 37 | |Int|12.23 μs|121.5 μs|1.227 ms|12.25 ms|122.9 ms| 38 | |Integer|16.20 μs|161.4 μs|1.618 ms|16.15 ms|160.8 ms| 39 | 40 | ## Decimal division 41 | 42 | |Name|10|100|1000| 43 | |---|---|---|---| 44 | |Double|0.030 μs|0.287 μs|0.003 ms| 45 | |Rational|0.953 μs|11.23 μs|0.150 ms| 46 | |Scientific|7.654 μs|737.3 μs|109.8 ms| 47 | -------------------------------------------------------------------------------- /Report.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ScopedTypeVariables #-} 2 | {-# OPTIONS_GHC -fno-warn-name-shadowing #-} 3 | {-# LANGUAGE ExistentialQuantification #-} 4 | {-# LANGUAGE BangPatterns #-} 5 | 6 | module Main (main) where 7 | 8 | import Control.DeepSeq 9 | import Data.Function 10 | import Data.List 11 | import System.Environment 12 | import Text.CSV 13 | import Text.Printf 14 | 15 | main :: IO () 16 | main = do 17 | fp:_ <- getArgs 18 | reportFromCsv fp 19 | 20 | reportFromCsv :: FilePath -> IO () 21 | reportFromCsv fp = do 22 | result <- parseCSVFromFile fp 23 | case result of 24 | Right (_:rows) -> do 25 | !readme <- fmap force (readFile "README.md") 26 | let sep = "" 27 | before = unlines (takeWhile (/= sep) (lines readme) ++ [sep ++ "\n"]) 28 | writeFile 29 | "README.md" 30 | (before ++ 31 | unlines 32 | (map 33 | format 34 | (filter 35 | (not . null . filter (not . null . filter (not . null))) 36 | (groupBy (on (==) (takeWhile (/= '/') . concat . take 1)) rows)))) 37 | _ -> error "Couldn't parse csv" 38 | 39 | format :: [[String]] -> String 40 | format rows = 41 | ("## " ++ takeWhile (/= '/') (concat (concat (take 1 (drop 1 rows))))) ++ 42 | "\n\n" ++ 43 | unlines 44 | [ ("|Name|" ++ intercalate "|" scales ++ "|") 45 | , "|" ++ concat (replicate (1 + length scales) "---|") 46 | ] ++ 47 | unlines 48 | (map 49 | (\name -> 50 | "|" ++ name ++ "|" ++ intercalate "|" (valuesByName name) ++ "|") 51 | (names)) 52 | where 53 | valuesByName name = 54 | map 55 | (\row@(_:mean:_) -> 56 | let scale = rowScale row 57 | in float (valuesByScale scale) (read mean)) 58 | (filter ((== name) . rowName) rows) 59 | valuesByScale scale = 60 | map (\(_:mean:_) -> read mean) (filter ((== scale) . rowScale) rows) 61 | names = nub (map rowName rows) 62 | scales = nub (map rowScale rows) 63 | rowName row = 64 | let s = 65 | takeWhile 66 | (/= ':') 67 | (dropWhile (== '/') (dropWhile (/= '/') (concat (take 1 row)))) 68 | in s 69 | rowScale row = 70 | let scale = dropWhile (== ':') (dropWhile (/= ':') (concat (take 1 row))) 71 | in scale 72 | 73 | float :: [Double] -> Double -> String 74 | float others x = let (scale, ext) = secs (mean others) 75 | in with (x * scale) ext 76 | 77 | -- | Convert a number of seconds to a string. The string will consist 78 | -- of four decimal places, followed by a short description of the time 79 | -- units. 80 | secs :: Double -> (Double, String) 81 | secs k 82 | | k >= 1 = 1 `pair` "s" 83 | | k >= 1e-3 = 1e3 `pair` "ms" 84 | | k >= 1e-6 = 1e6 `pair` "μs" 85 | | k >= 1e-9 = 1e9 `pair` "ns" 86 | | k >= 1e-12 = 1e12 `pair` "ps" 87 | | k >= 1e-15 = 1e15 `pair` "fs" 88 | | k >= 1e-18 = 1e18 `pair` "as" 89 | | otherwise = error "Bad scale" 90 | where pair= (,) 91 | 92 | with :: Double -> String -> String 93 | with (t :: Double) (u :: String) 94 | | t >= 1e9 = printf "%.4g %s" t u 95 | | t >= 1e3 = printf "%.0f %s" t u 96 | | t >= 1e2 = printf "%.1f %s" t u 97 | | t >= 1e1 = printf "%.2f %s" t u 98 | | otherwise = printf "%.3f %s" t u 99 | 100 | -- | Simple rolling average. 101 | mean :: [Double] -> Double 102 | mean = 103 | snd . 104 | foldr 105 | (\x (cnt,avg) -> 106 | ( cnt + 1 107 | , (x + avg * cnt) / (cnt + 1))) 108 | (0, 0) 109 | -------------------------------------------------------------------------------- /Time.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE BangPatterns #-} 2 | {-# LANGUAGE ScopedTypeVariables #-} 3 | module Main (main) where 4 | 5 | import Control.Monad 6 | import Criterion.Main 7 | import Criterion.Types 8 | import Data.Char 9 | import Data.Scientific 10 | import System.Directory 11 | 12 | main :: IO () 13 | main = do 14 | let fp = "out.csv" 15 | exists <- doesFileExist fp 16 | when exists (removeFile fp) 17 | defaultMainWith 18 | defaultConfig {csvFile = Just fp} 19 | [ bgroup 20 | "Addition" 21 | (concat 22 | [ [ bench ("Int:" ++ scale i) (whnf add'Int (-i)) 23 | | i <- [1, 10, 100, 1000, 10000, 100000, 1000000] 24 | ] 25 | , [ bench ("Double:" ++ scale i) (whnf add'Double (-i)) 26 | | i <- [1, 10, 100, 1000, 10000, 100000, 1000000] 27 | ] 28 | , [ bench ("Integer:" ++ scale i) (whnf add'Integer (-i)) 29 | | i <- [1, 10, 100, 1000, 10000, 100000, 1000000] 30 | ] 31 | , [ bench ("Rational:" ++ scale i) (whnf add'Rational (-i)) 32 | | i <- [1, 10, 100, 1000, 10000, 100000, 1000000] 33 | ] 34 | , [ bench ("Scientific:" ++ scale i) (whnf add'Scientific (-i)) 35 | | i <- [1, 10, 100, 1000, 10000, 100000, 1000000] 36 | ] 37 | ]) 38 | , bgroup 39 | "Subtraction" 40 | (concat 41 | [ [ bench ("Int:" ++ scale i) (whnf subtract'Int i) 42 | | i <- [1, 10, 100, 1000, 10000, 100000, 1000000] 43 | ] 44 | , [ bench ("Double:" ++ scale i) (whnf subtract'Double i) 45 | | i <- [1, 10, 100, 1000, 10000, 100000, 1000000] 46 | ] 47 | , [ bench ("Integer:" ++ scale i) (whnf subtract'Integer i) 48 | | i <- [1, 10, 100, 1000, 10000, 100000, 1000000] 49 | ] 50 | , [ bench ("Rational:" ++ scale i) (whnf subtract'Rational i) 51 | | i <- [1, 10, 100, 1000, 10000, 100000, 1000000] 52 | ] 53 | , [ bench ("Scientific:" ++ scale i) (whnf subtract'Scientific i) 54 | | i <- [1, 10, 100, 1000, 10000, 100000, 1000000] 55 | ] 56 | ]) 57 | , bgroup 58 | "Integer division" 59 | (concat 60 | [ [ bench ("Int:" ++ scale i) (whnf div'Int i) 61 | | i <- [1000, 10000, 100000, 1000000, 10000000] 62 | ] 63 | , [ bench ("Integer:" ++ scale i) (whnf div'Integer i) 64 | | i <- [1000, 10000, 100000, 1000000, 10000000] 65 | ] 66 | ]) 67 | , bgroup 68 | "Decimal division" 69 | (concat 70 | [ [ bench ("Double:" ++ scale i) (whnf div'Double i) 71 | | i <- [10, 100, 1000] 72 | ] 73 | , [ bench ("Rational:" ++ scale i) (whnf div'Rational i) 74 | | i <- [10, 100, 1000] 75 | ] 76 | , [ bench ("Scientific:" ++ scale i) (whnf div'Scientific i) 77 | | i <- [10, 100, 1000] 78 | ] 79 | ]) 80 | ] 81 | where 82 | subtract'Integer :: Integer -> () 83 | subtract'Integer 0 = () 84 | subtract'Integer a = subtract'Integer (a - 1) 85 | subtract'Int :: Int -> () 86 | subtract'Int 0 = () 87 | subtract'Int a = subtract'Int (a - 1) 88 | subtract'Rational :: Rational -> () 89 | subtract'Rational 0 = () 90 | subtract'Rational a = subtract'Rational (a - 1) 91 | subtract'Double :: Double -> () 92 | subtract'Double 0 = () 93 | subtract'Double a = subtract'Double (a - 1) 94 | subtract'Scientific :: Scientific -> () 95 | subtract'Scientific 0 = () 96 | subtract'Scientific a = subtract'Scientific (a - 1) 97 | add'Integer :: Integer -> () 98 | add'Integer 0 = () 99 | add'Integer a = add'Integer (a + 1) 100 | add'Int :: Int -> () 101 | add'Int 0 = () 102 | add'Int a = add'Int (a + 1) 103 | add'Rational :: Rational -> () 104 | add'Rational 0 = () 105 | add'Rational a = add'Rational (a + 1) 106 | add'Double :: Double -> () 107 | add'Double 0 = () 108 | add'Double a = add'Double (a + 1) 109 | add'Scientific :: Scientific -> () 110 | add'Scientific 0 = () 111 | add'Scientific a = add'Scientific (a + 1) 112 | div'Rational :: Int -> Rational 113 | div'Rational i = go i (fromIntegral i) 114 | where 115 | go 0 (!c) = c 116 | go i (!c) = go (i - 1) (c / 2) 117 | div'Scientific :: Int -> Scientific 118 | div'Scientific i = go i (fromIntegral i) 119 | where 120 | go 0 (!c) = c 121 | go i (!c) = go (i - 1) (c / 2) 122 | div'Double :: Int -> Double 123 | div'Double i = go i (fromIntegral i) 124 | where 125 | go 0 (!c) = c 126 | go i (!c) = go (i - 1) (c / 2) 127 | div'Int :: Int -> Int 128 | div'Int i = go i (fromIntegral i) 129 | where 130 | go 0 (!c) = c 131 | go i (!c) = go (i - 1) (div c 2) 132 | div'Integer :: Int -> Integer 133 | div'Integer i = go i (fromIntegral i) 134 | where 135 | go 0 (!c) = c 136 | go i (!c) = go (i - 1) (div c 2) 137 | 138 | -- | Show without any % 1 or .0 extensions. 139 | scale :: Show a => a -> String 140 | scale = takeWhile isDigit . show 141 | -------------------------------------------------------------------------------- /bench.cabal: -------------------------------------------------------------------------------- 1 | name: bench 2 | version: 0 3 | build-type: Simple 4 | cabal-version: >=1.10 5 | 6 | library 7 | build-depends: base 8 | default-language: Haskell2010 9 | 10 | benchmark time 11 | default-language: Haskell2010 12 | type: exitcode-stdio-1.0 13 | ghc-options: -Wall -O2 -rtsopts 14 | main-is: Time.hs 15 | build-depends: base 16 | , directory, scientific 17 | , ghc-prim 18 | , criterion 19 | , deepseq 20 | 21 | executable report 22 | default-language: Haskell2010 23 | ghc-options: -Wall -O2 -rtsopts 24 | main-is: Report.hs 25 | build-depends: base 26 | , bytestring, directory 27 | , vector 28 | , ghc-prim 29 | , criterion 30 | , deepseq 31 | , containers 32 | , csv 33 | 34 | test-suite space 35 | default-language: Haskell2010 36 | type: exitcode-stdio-1.0 37 | ghc-options: -O2 38 | main-is: Space.hs 39 | build-depends: base 40 | , weigh 41 | , deepseq 42 | , random 43 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-8.5 2 | extra-deps: 3 | - containers-0.5.10.1 4 | --------------------------------------------------------------------------------