8

Consider the following data model utilizing an existential:

data Node a = Node a (Map TypeRep AnyNode)
data AnyNode = forall a. Show a => AnyNode a

The rules about memory footprint of standard types have been explained previously. Now, what are the rules for existential types, like AnyNode?

Are there any optimization techniques, e.g. some workarounds using unsafeCoerce making it possible to elude the existential declaration? I'm asking this because a type similar to Node is going to be placed in a cost centre of a highly memory-intensive lib, so memory footprint is all, that's why the most dirty hacks are welcome.

Community
  • 1
  • 1
Nikita Volkov
  • 41,289
  • 10
  • 85
  • 162

1 Answers1

15

The ghc-datasize package may be of help here:

{-# LANGUAGE RankNTypes, GADTs #-}

import GHC.DataSize

data Node = forall a. Show a => Node a 

main = do
    s <- closureSize $ Node 0 
    print s -- 24 bytes on my 64-bit system

So, it seems that Node takes one extra word compared to the plain unary data constructor, presumably because of the Show class dictionary pointer. Also, I tried adding more class constraints to Node, and each of them takes one extra word of space.

I don't know for sure whether it is possible to magic away the dictionary pointer in specific circumstances. I think it isn't possible if you'd like to keep the existential type.

András Kovács
  • 29,038
  • 3
  • 45
  • 94
  • That is one damn useful tool! Just playing with it in GHCi for a few minutes has opened my eyes on some aspects. E.g., a single `TypeRep` weighs 408 bytes - OMG, no thanks, I'll use a custom dictionary! However this doesn't completely answer my question, so I'll leave it open. Thanks a lot! – Nikita Volkov Nov 26 '13 at 15:56
  • @NikitaVolkov are you sure the `TypeRep`s are not shared (i.e. only one per program and type)? – tibbe Nov 26 '13 at 17:10
  • @tibbe [According to docs on `recursiveSize`](http://hackage.haskell.org/package/ghc-datasize-0.1.1/docs/GHC-DataSize.html), _the actual size in memory is calculated, so shared values are only counted once_. Calling `recursiveSize [typeOf 'a', typeOf 'b']` gives `520`, and `recursiveSize [typeOf 'a']` - `272`. So it looks like no, they aren't shared, unfortunately. – Nikita Volkov Nov 26 '13 at 17:26
  • 2
    @NikitaVolkov: you may also want to muck around in compiled mode, possibly with optimizations, because the sizes sometimes change compared to GHCi. – András Kovács Nov 26 '13 at 17:39
  • @NikitaVolkov that sounds wrong. Can you file a bug against GHC? – tibbe Nov 27 '13 at 17:51
  • @tibbe I've just retested this stuff in "-O2" mode and `TypeRep` of absolutely anything seems to be weighing just 16 bytes, i.e. 2 words on my 64-bit machine. Thanks for that bump! – Nikita Volkov Nov 27 '13 at 18:10