When answering a question with a suggestion to use GADTs, some questions with regards to performance came up in the comments. The question involved a typeclass PlotValue
:
class PlotValue a where
value :: a -> Double
and my answer suggested using a GADT Input
:
data Input where
Input :: (PlotValue a, PlotValue b) => Maybe a -> Maybe b -> Input
but the details are, I suppose, immaterial.
I'm wondering about two aspects of performance:
Time: Are there any runtime costs incurred by a pattern match like
case x of
Input (Just a) (Just b) -> value a * value b
_ -> 0.0
over and above the normal costs matching two Maybe
values?
Space:
How much storage overhead does a value of type Input
carry? My guess is that it carries the two PlotValue
dictionaries around for each and every value of type Input
(a 'pointer' each?), meaning that a [Input]
will be much more inefficient in terms of memory use than using (Just Double, Just Double)
or, better yet, (# #Double, #Double #)
- if you store the results of value
in a normal or unpacked tuple.
So, while I love the expressiveness GADTs give me, I've never thought about the performance aspects much. Can anyone tell me more about this (and any other hidden costs I might not be aware of)?
I think you've nailed down the overheads. For each existentially qualified variable you need the corresponding (pointer to) dictionaries. This takes space, and worse, the method calls are going to be slow. The (*) in your example will be an indirect function call, whereas with Double it would have been a primitive op.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With