我为下面的数据类型写了一个可存储的向量实例(这里的原始问题):
data Atoms = I GHC.Int.Int32 | S GHC.Int.Int16
为可存储向量定义这些实例的代码如下。虽然我使用下面的代码获得了非常好的性能,但我对提高该可存储实例的性能的通用建议非常感兴趣。通过一般性建议,我的意思是:
- 它不特定于 GHC 编译器版本。您可以假设 GHC 6.12.3+ 排除了早期版本中存在的性能错误,并且与此处的代码相关。
- 特定于平台的建议是可以的。您可以假设 x86_64 Linux 平台。
- 比利用硬件特定优化的建议更有价值的是算法改进(大 O)形式的通用建议。但是,鉴于这里的 peek/poke 等基本操作,据我所知,算法改进的余地不大(因此更有价值,因为它是一种稀缺商品:)
- x86_64 的编译器标志是可接受的(例如,告诉编译器删除浮点安全检查等)。我正在使用“-O2 --make”选项来编译代码。
如果有任何已知的优秀库源代码可以做类似的事情(即,为联合/递归数据类型定义可存储实例),我将对检查它们非常感兴趣。
import Data.Vector.Storable
import qualified Data.Vector.Storable as V
import Foreign
import Foreign.C.Types
import GHC.Int
data Atoms = I GHC.Int.Int32 | S GHC.Int.Int16
deriving (Show)
instance Storable Atoms where
sizeOf _ = 1 + sizeOf (undefined :: Int32)
alignment _ = 1 + alignment (undefined :: Int32)
{-# INLINE peek #-}
peek p = do
let p1 = (castPtr p::Ptr Word8) `plusPtr` 1 -- get pointer to start of the element. First byte is type of element
t <- peek (castPtr p::Ptr Word8)
case t of
0 -> do
x <- peekElemOff (castPtr p1 :: Ptr GHC.Int.Int32) 0
return (I x)
1 -> do
x <- peekElemOff (castPtr p1 :: Ptr GHC.Int.Int16) 0
return (S x)
{-# INLINE poke #-}
poke p x = case x of
I a -> do
poke (castPtr p :: Ptr Word8) 0
pokeElemOff (castPtr p1) 0 a
S a -> do
poke (castPtr p :: Ptr Word8) 1
pokeElemOff (castPtr p1) 0 a
where p1 = (castPtr p :: Ptr Word8) `plusPtr` 1 -- get pointer to start of the element. First byte is type of element
更新:
根据 Daniel 和 dflemstr 的反馈,我重写了对齐方式,并将构造函数更新为 Word32 类型而不是 Word8。但是,似乎要使其有效,数据构造函数也应该更新为具有未打包的值——这是我的疏忽。我应该首先编写数据构造函数来获得解压缩的值(参见John Tibbell 的性能幻灯片- 幻灯片 #49)。因此,重写数据构造函数,再加上对齐和构造函数的更改,对性能产生了很大影响,将函数比向量提高了大约 33%(我的基准测试中的一个简单的 sum 函数)。以下相关更改(警告 - 不可移植,但对我的用例来说不是问题):
数据构造函数更改:
data Atoms = I {-# UNPACK #-} !GHC.Int.Int32 | S {-# UNPACK #-} !GHC.Int.Int16
可存储的 sizeof 和对齐更改:
instance Storable Atoms where
sizeOf _ = 2*sizeOf (undefined :: Int32)
alignment _ = 4
{-# INLINE peek #-}
peek p = do
let p1 = (castPtr p::Ptr Word32) `plusPtr` 1
t <- peek (castPtr p::Ptr Word32)
case t of
0 -> do
x <- peekElemOff (castPtr p1 :: Ptr GHC.Int.Int32) 0
return (I x)
_ -> do
x <- peekElemOff (castPtr p1 :: Ptr GHC.Int.Int16) 0
return (S x)
{-# INLINE poke #-}
poke p x = case x of
I a -> do
poke (castPtr p :: Ptr Word32) 0
pokeElemOff (castPtr p1) 0 a
S a -> do
poke (castPtr p :: Ptr Word32) 1
pokeElemOff (castPtr p1) 0 a
where p1 = (castPtr p :: Ptr Word32) `plusPtr` 1