Fast conversion between Vector Double and Vector CDouble

Hello, I just rewrote my levmar library[1] to use Vectors (from the vector package[2]) instead of lists. I was expecting to see a significant performance improvement. Unfortunately I only saw a 10% improvement. However, I noticed I had a lot of conversions from Vector Double to Vector CDouble and visa versa in my code: import Data.Vector.Storable ( Vector ) mapRealToFrac ∷ (Storable α, Storable β, Real α, Fractional β) ⇒ Vector α → Vector β mapRealToFrac = VS.map realToFrac When I replace this with: mapRealToFrac = unsafeCoerce My application computes the same result but does it 28 times faster! My question are: 1) Is this always safe? In other words: are the runtime representations of Double and CDouble always equivalent or do they vary between platforms? 2) Can the same improvement be accomplished using RULE pragma's? 3) Are there disadvantages of using CDouble instead of Double in the levmar API? For some reason it feels wrong to use CDouble in the API but I don't have a good argument against it yet. Thanks, Bas [1] http://hackage.haskell.org/package/levmar [2] http://hackage.haskell.org/package/vector

On Monday 04 April 2011 13:54:39, Bas van Dijk wrote:
Hello,
I just rewrote my levmar library[1] to use Vectors (from the vector package[2]) instead of lists. I was expecting to see a significant performance improvement. Unfortunately I only saw a 10% improvement. However, I noticed I had a lot of conversions from Vector Double to Vector CDouble and visa versa in my code:
import Data.Vector.Storable ( Vector )
mapRealToFrac ∷ (Storable α, Storable β, Real α, Fractional β) ⇒ Vector α → Vector β mapRealToFrac = VS.map realToFrac
When I replace this with:
mapRealToFrac = unsafeCoerce
My application computes the same result but does it 28 times faster!
My question are:
1) Is this always safe? In other words: are the runtime representations of Double and CDouble always equivalent or do they vary between platforms?
It's not always safe, it seems to be different for NHC. In Foreign.C.Types, you find #ifndef __NHC__ <snip> -- | These types are are represented as @newtype@s of <paraphrase> the corresponding Haskell types <snip> #else <snip> #endif for the integral and floating types. So for the time being, it's safe except possibly on NHC, as long as it is (and can be) only used to convert between the corresponding Haskell and C types (so #ifdef __NHC__ mapRealToFrac = VS.map realToFrac #else mapRealToFrac = unsafeCoerce #endif would also cater for NHC).
2) Can the same improvement be accomplished using RULE pragma's?
For GHC, probably, but rule-firings aren't always predictable. For example, it could be that the rule for realToFrac fires first.
3) Are there disadvantages of using CDouble instead of Double in the levmar API?
You'd have to write the conversion code every time you use it from Haskell, wouldn't you?
For some reason it feels wrong to use CDouble in the API but I don't have a good argument against it yet.
Thanks,
Bas
[1] http://hackage.haskell.org/package/levmar [2] http://hackage.haskell.org/package/vector

On 4 April 2011 14:41, Daniel Fischer
On Monday 04 April 2011 13:54:39, Bas van Dijk wrote:
Hello,
I just rewrote my levmar library[1] to use Vectors (from the vector package[2]) instead of lists. I was expecting to see a significant performance improvement. Unfortunately I only saw a 10% improvement. However, I noticed I had a lot of conversions from Vector Double to Vector CDouble and visa versa in my code:
import Data.Vector.Storable ( Vector )
mapRealToFrac ∷ (Storable α, Storable β, Real α, Fractional β) ⇒ Vector α → Vector β mapRealToFrac = VS.map realToFrac
When I replace this with:
mapRealToFrac = unsafeCoerce
My application computes the same result but does it 28 times faster!
My question are:
1) Is this always safe? In other words: are the runtime representations of Double and CDouble always equivalent or do they vary between platforms?
It's not always safe, it seems to be different for NHC. In Foreign.C.Types, you find
#ifndef __NHC__ <snip> -- | These types are are represented as @newtype@s of <paraphrase> the corresponding Haskell types <snip> #else <snip> #endif
for the integral and floating types. So for the time being, it's safe except possibly on NHC, as long as it is (and can be) only used to convert between the corresponding Haskell and C types (so
#ifdef __NHC__ mapRealToFrac = VS.map realToFrac #else mapRealToFrac = unsafeCoerce #endif
would also cater for NHC).
2) Can the same improvement be accomplished using RULE pragma's?
For GHC, probably, but rule-firings aren't always predictable. For example, it could be that the rule for realToFrac fires first.
3) Are there disadvantages of using CDouble instead of Double in the levmar API?
You'd have to write the conversion code every time you use it from Haskell, wouldn't you?
For some reason it feels wrong to use CDouble in the API but I don't have a good argument against it yet.
Thanks,
Bas
[1] http://hackage.haskell.org/package/levmar [2] http://hackage.haskell.org/package/vector
Thanks for the clarification Daniel. I just read[1] it's also possible to directly use Double and Float in an FFI import declaration. I always assumed you could only use the C types from Foreign.C.Types. So I think I'm going to change bindings-levmar[2] to use Double and Float instead of CDouble and CFloat. This way I don't even need to map. Bas [1] http://www.haskell.org/onlinereport/haskell2010/haskellch8.html#x15-1560008.... [2] http://hackage.haskell.org/package/bindings-levmar

On Mon, Apr 4, 2011 at 6:41 AM, Bas van Dijk
Thanks for the clarification Daniel.
I just read[1] it's also possible to directly use Double and Float in an FFI import declaration. I always assumed you could only use the C types from Foreign.C.Types. So I think I'm going to change bindings-levmar[2] to use Double and Float instead of CDouble and CFloat. This way I don't even need to map.
It's just that on some platforms/Haskell implementations the Haskell Double/Float may not line up with the C side. If you use CDouble/CFloat it will line up. Compared to unsafeCoerce, I would hope you'd get a C compile error on those platforms, but I don't know for certain if that's the case. In my opinion, it's the conversion functions that need to be fixed. Jason

On 2011-04-04 13:54 +0200, Bas van Dijk wrote:
However, I noticed I had a lot of conversions from Vector Double to Vector CDouble and visa versa in my code:
import Data.Vector.Storable ( Vector )
mapRealToFrac ∷ (Storable α, Storable β, Real α, Fractional β) ⇒ Vector α → Vector β mapRealToFrac = VS.map realToFrac
When I replace this with:
mapRealToFrac = unsafeCoerce
My application computes the same result but does it 28 times faster!
Note that even if Double and CDouble have identical representations, unsafeCoerce does not perform the same conversion as realToFrac -- the latter does conversion to/from Rational and thus munges all values not representable therein. This also happens to be why it is slooooooooow. Some real examples, in GHCi:
realToFrac (0/0 :: Double) :: CDouble -Infinity
unsafeCoerce (0/0 :: Double) :: CDouble NaN
realToFrac (-0 :: Double) :: CDouble 0.0
unsafeCoerce (-0 :: Double) :: CDouble -0.0
Using realToFrac to convert between different floating types is even more fun:
realToFrac (1/0 :: Float) :: Double 3.402823669209385e38
Nice!
My question are:
1) Is this always safe? In other words: are the runtime representations of Double and CDouble always equivalent or do they vary between platforms?
Probably not, but realToFrac isn't really "safe", either (as above).
2) Can the same improvement be accomplished using RULE pragma's?
No, because the two methods do not compute the same function. However, there are (or were) broken RULE pragmas in GHC which do this sort of transformation. Such RULEs make realToFrac really fun because your program's correctness will depend on whether or not GHC decides to inline things. -- Nick Bowler, Elliptic Technologies (http://www.elliptictech.com/)
participants (4)
-
Bas van Dijk
-
Daniel Fischer
-
Jason Dagit
-
Nick Bowler