bjornbm / dimensional

Dimensional library variant built on Data Kinds, Closed Type Families, TypeNats (GHC 7.8+).
BSD 3-Clause "New" or "Revised" License
102 stars 15 forks source link

Document expected overhead #177

Open arekfu opened 7 years ago

arekfu commented 7 years ago

How much overhead does dimensional introduce? I understand that unit homogeneity is statically checked, but is there an overhead for unit manipulation (arithmetics, etc.)? Is it possible to unbox ({-# UNPACK #-}) dimensionful quantities in data structures where the corresponding dimensionless quantity would be unboxable? It would be nice to have a sentence or two in the README file.

dmcclean commented 7 years ago

I also would like to know this. Unfortunately my Haskell performance measurement knowledge isn't there to know how to find out.

The GHC documentation (https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#unpack-pragma) says that the UNPACK machinery can see through newtypes, but I'm not sure whether it can see through the type synonyms and associated data family to realize that Length Double => Quantity DLength Double => Dimensional DQuantity DLength Double is ultimately a newtype, and again I don't know how to find out.

Is this an area you have experience with? If you'd be willing to contribute, it seems like we definitely could use better benchmarks and documentation of their results.

bjornbm commented 7 years ago

One way could be to write some benchmarks (using criterion?) where we perform the equivalent calculations using dimensional and using straight Doubles and compare the timing.

The original (2006-ish) idea was that with the newtypes instead of data all the type information would get removed at compile time and all that would be left would be arithmetic on Doubles (or whatever the underlying data type was). I never did benchmark as far as I can recall, nor am I sure to what extent this idea is valid with the current feature set. It may also never have been true when you get to functors and such.

dmcclean commented 7 years ago

Definitely this is what we need to do, I'm just not clear on how to do it. In particular I don't know how to write it so that it measures the performance of the arithmetic ignoring the performance of constructing the Quantitys in the first place, just to separate concerns. (Because *~ meter requires a multiplication by 1, we could try to get really clever in *~~ and skip it but I'd like to have performance measurements justifying it.)

Even in the new design with named units, Quantity is still a newtype. (+) :: Quantity d a -> Quantity d a -> Quantity d a is defined to be (+) = liftQ2 (Prelude.+) = coerce (Prelude.+), so I would expect that we should have similar performance, but again I don't know how to measure it.

Multiplication and division is a different story, since we have overloaded versions that also operate on Units and thus do a bunch of rigamarole. If we had benchmarks to justify and test it, we might benefit from some sort of RULES pragma specializing (*) at type Quantity d1 a -> Quantity d2 a -> Quantity d3 a to a similar implementation, but again I don't know how to write it.

dmcclean commented 7 years ago

I don't know what I'm doing wrong, but it's obviously at least one thing because it says that the wrapped version is 5x faster.

Anybody see what's going on? https://github.com/dmcclean/dimensional/blob/improved-benchmarks/benchmarks/Main.hs

bjornbm commented 7 years ago

Strange indeed. Try these and note the difference between RawSum1 and RawSum2:

         [ bench "Sum1" $ nf sum xs
         , bench "Sum2" $ nf (P.sum . (/~~ siUnit)) xs
         , bench "RawSum1" $ nf P.sum xs'
         , bench "RawSum2" $ nf (P.sum . id) xs'
         , bench "RawSum3" $ nf (foldr (P.+) 0) xs'
         ]

(By the way, it seems the force in setupEnv is doing nothing.)

dmcclean commented 6 years ago

inspection-testing may also be an approach here, for things that are supposed to be zero-cost.