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
Conversation
benchmark label _ = do | ||
let benchMaxElems = min 1024 (fromEnum (maxBound @k)) | ||
let upBound = foldl' (.) id (replicate benchMaxElems succ) minBound | ||
let map_keys :: [k] = [minBound..upBound] |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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...
So I think that you're doing great job here 👍
There was a problem hiding this 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 |
There was a problem hiding this comment.
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 👍
There was a problem hiding this comment.
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 😅
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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-benchmarks
s Main module?
There was a problem hiding this comment.
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
containers-int-strict/src/Map/Int.hs
Outdated
import qualified Data.IntMap.Strict as M | ||
import Prelude hiding (lookup, null) | ||
|
||
newtype Map k v = IM (M.IntMap v) | ||
deriving newtype (Show, Eq) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
build-depends: base, primitive-containers >= 0.3.0, deepseq | |
build-depends: base | |
, deepseq ^>= 1.4 | |
, primitive-containers ^>= 0.3.0 |
There was a problem hiding this comment.
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 :)
containers-primitive/src/Map/Prim.hs
Outdated
@@ -20,6 +20,8 @@ module Map.Prim | |||
, elems | |||
) where | |||
|
|||
import Control.DeepSeq (NFData (..)) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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...
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
Hi @chshersh, thank you for the detailed review. After applying your review code seems to work well, but there is a concern:
|
@yigitozkavci Let's solve problem with |
There was a problem hiding this 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!
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
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?Map.Prim
module uses Data.Map.Lifted.Lifted behind the scenes and it does not haveNFData
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"?