Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Benchmarks #43

Merged
merged 15 commits into from Oct 31, 2018
Merged

Introduce Benchmarks #43

merged 15 commits into from Oct 31, 2018

Conversation

yigitozkavci
Copy link
Contributor

Closes #11

Purpose

Adding benchmarks for assessing runtimes of Map.lookup operation of several packages.

Running the Benchmarks

A new cabal project and executable is added. You can run the benchmarks with:
cabal new-run benchmarks-exe

Questions

  • There are more operations other than just lookup operation we can do benchmarking for. Shall we add more benchmarks targeting those? If so, shall it be in this PR or in separate PRs?
  • Shall we add benchmarks to Travis CI to run?
  • Since Map.Prim module uses Data.Map.Lifted.Lifted behind the scenes and it does not have NFData instance, I could not introduce benchmarks for the module. Can we think of a workaround or do we say "It does make sense that we cannot write a benchmark for it"?

benchmark label _ = do
let benchMaxElems = min 1024 (fromEnum (maxBound @k))
let upBound = foldl' (.) id (replicate benchMaxElems succ) minBound
let map_keys :: [k] = [minBound..upBound]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The complexity here exists because I wanted to be able to write benchmarks for any k such that (Enum k, Bounded k, Key k, NFData (Map k Int)) holds. I think it's a good choice but the choice is arguable, obviously.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra complexity is okay if it allows to have polymoprhic implementation. I see lookup benchmarks from the haskell-perf/dictionaries and I'm horrified by their code...

https://github.com/haskell-perf/dictionaries/blob/2bbc950e9597eea74130a714662b9b2c173ecd02/Time.hs#L307-L321

So I think that you're doing great job here 👍

@chshersh chshersh added the bench label Oct 29, 2018
@chshersh chshersh added this to In progress in #2: Hacktoberfest (October, 2018) via automation Oct 29, 2018
@chshersh chshersh added the Hacktoberfest https://hacktoberfest.digitalocean.com/ label Oct 29, 2018
Copy link
Contributor

@chshersh chshersh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! @yigitozkavci sorry for not returning back to you fast, I was quite busy in last couple days...

I think that existing implementation can be polished a little bit more. But it's really great that you scaffolded initial benchmark suite so later we can just add more benchmarks and improve existing onese 👍

benchmark
:: forall k. (Enum k, Bounded k, Key k, NFData (Map k Int))
=> String
-> Proxy k
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing Proxy k here you can just not pass it and enable AllowAmbiguousTypes language extension. So instead of passing:

benchmark "some label" (Proxy @T)

you can call this function just as:

benchmark @T "some label"

-XTypeApplications FTW 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I chose not to enable the AllowAmbiguousTypes extension but in this case it might be okay 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Despite the name, AllowAmbiguousTypes is actually a completely safe extension 👌 You only can use it with TypeApplications but TypeApplications is also safe.

import Gauge.Main (Benchmark, bench, defaultMain, whnf)
import Relude hiding (fromList, Map)

benchmark
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename this benchmark in a way that it tells about what it does, like benchmarkSequentialLookup.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about this function as if it runs "all" the benchmarks a module will have. In this case, we only have lookupBench and this function runs it only. Do you think we should expose multiple benchmark functions and run them from the containers-sig-benchmarkss Main module?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yigitozkavci Hmm, if this is the function that runs all benchmarks than okay

import qualified Data.IntMap.Strict as M
import Prelude hiding (lookup, null)

newtype Map k v = IM (M.IntMap v)
deriving newtype (Show, Eq)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of writing NFData instance manually you can add it to this list in deriving newtype

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, never used DerivingStrategies before, beautiful!

@@ -23,7 +23,7 @@ library
exposed-modules: Map.Prim
reexported-modules: Map.Prim as Map

build-depends: base, primitive-containers >= 0.3.0
build-depends: base, primitive-containers >= 0.3.0, deepseq
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
build-depends: base, primitive-containers >= 0.3.0, deepseq
build-depends: base
, deepseq ^>= 1.4
, primitive-containers ^>= 0.3.0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did not know about cabal 2.0's caret operator, great, I applied the diff :)

@@ -20,6 +20,8 @@ module Map.Prim
, elems
) where

import Control.DeepSeq (NFData (..))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that this import is not used here. Probably it's not needed. As well as deepseq dependency for the containers-primitive package.

main = do
BIRO.benchmark "intmap" (Proxy @K)

BORO.benchmark "ordmap" (Proxy @K)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like benchmarks for primitive-containers are missing here...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I included this matter in "Questions" section of the PR description, I simply could not figure out how I could implement it since Prim uses Data.Map.Lifted.Lifted inside.

benchmark label _ = do
let benchMaxElems = min 1024 (fromEnum (maxBound @k))
let upBound = foldl' (.) id (replicate benchMaxElems succ) minBound
let map_keys :: [k] = [minBound..upBound]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra complexity is okay if it allows to have polymoprhic implementation. I see lookup benchmarks from the haskell-perf/dictionaries and I'm horrified by their code...

https://github.com/haskell-perf/dictionaries/blob/2bbc950e9597eea74130a714662b9b2c173ecd02/Time.hs#L307-L321

So I think that you're doing great job here 👍

-> IO ()
benchmark label _ = do
let benchMaxElems = min 1024 (fromEnum (maxBound @k))
let upBound = foldl' (.) id (replicate benchMaxElems succ) minBound
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can simplify benchmarks a lot if you pass lower and upper bounds to this function. So then in main we can just create variables:

let lookupRangeLower = 0
let lookupRangeUpper = 1024

and pass them to this function. So the approach is more flexible because we can easily change the range and test with other range.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm great, this is the kinda-same thing we've done with the type alias K. Will do 👍

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeApplications #-}

module Map.Contrib.Bench where
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you, please, explicit export list to this module so it will become more clear what are the internals and what is the exposed interface?

=> String
-> Proxy k
-> IO ()
benchmark label _ = do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that structureName is better name for the label variable

@yigitozkavci
Copy link
Contributor Author

Hi @chshersh, thank you for the detailed review. After applying your review code seems to work well, but there is a concern:

  • Because I cannot write a NFData instance for Map.Prim, we cannot have the constraint in Map.hsig file since it breaks the property tests build (as you can see the failures in TravisCI in the past commits). We should either be able to write NFData for Map.Prim or we leave it as it is.

@chshersh
Copy link
Contributor

@yigitozkavci Let's solve problem with primitive-containers package under separate issue. Looks like this won't be an easy problem.

#2: Hacktoberfest (October, 2018) automation moved this from In progress to Reviewer approved Oct 31, 2018
Copy link
Contributor

@chshersh chshersh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yigitozkavci This looks really great! Looks like we can merge this. Thank you again very much!

@chshersh chshersh merged commit 267fcde into kowainik:master Oct 31, 2018
#2: Hacktoberfest (October, 2018) automation moved this from Reviewer approved to Done: PR's Oct 31, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bench Hacktoberfest https://hacktoberfest.digitalocean.com/
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

None yet

2 participants