从ByteString修复创建

时间:2018-01-12 01:00:21

标签: arrays performance haskell repa

最初我有一个ByteString,然后我将其解包并转换为Int16s,这部分过程需要相对较少的时间。然后我将Int16s列表转换为Repa数组,其中包含以下行

Repa.fromListUnboxed (Z :. bytesOfDataPerImage `div` 2) listOfInts

根据剖析器,这条线占用了大约40%的CPU时间,这可能只是表明我正在执行的计算并不能保证使用Repa。从ByteString到Repa阵列有更有效的路线吗?

我已经尝试过Repa fromByteString函数,虽然转换了

Array B DIM1 Word8 -> Array U DIM1 Int16

很慢。我通过首先将数组重新整形为2d数组Word8s然后折叠成Int16s来执行此操作。也许Byte数组是正确的方法,我的转换方法是错误的。

convertImageData :: Array B DIM1 Word8 -> Array U DIM1 Int16
convertImageData !arr = Repa.foldS convertWords 0 (Repa.map fromIntegral (splitArray arr))

splitArray :: Array B DIM1 Word8 -> Array U DIM2 Word8
splitArray !arr = computeUnboxedS $ reshape (Z :. ((size $ extent arr) `div` 2) :. 2) arr


convertWords :: Int16 -> Int16 -> Int16
convertWords !word1 !word2 = (word1 `shiftL` 8) .|. word2

对于某些上下文,该程序正在使用C / C ++编写的相同程序进行基准测试。

1 个答案:

答案 0 :(得分:1)

您最初转换为列表然后稍后调用Repa.fromListUnboxed的方法肯定很慢,因为您要做的只是强制列表中的元素,而不是依次将其加载到未装箱的数组中。这就是为什么转换到列表只需要很少的时间的原因,因为它所做的只是创建大量的thunk,但是实际的计算是在将其加载到数组中时进行的。

您的第二种方法肯定会更好,但是仍然有不必要的步骤,例如。不需要reshape数组,您只需将新大小传递给fromByteString函数即可。所以这是一个稍微改进的版本:

bytesToRepaOriginal :: Bytes.ByteString -> Repa.Array Repa.U Repa.DIM1 Int16
bytesToRepaOriginal bs =
  Repa.foldS
    convertWords
    0
    (Repa.map fromIntegral $
     Repa.fromByteString (Repa.Z Repa.:. (Bytes.length bs `div` 2) Repa.:. 2) bs)

fromByteString函数和Repa中的B表示由于某种原因并不是特别快,所以有一种更快的方法,即通过直接索引{{1}来构造数组}:

ByteString

使用bytesToRepaP :: Monad m => Bytes.ByteString -> m (Repa.Array Repa.U Repa.DIM1 Int16) bytesToRepaP bs = Repa.computeUnboxedP $ Repa.fromFunction (Repa.Z Repa.:. (Bytes.length bs `div` 2)) (\(Repa.Z Repa.:. i) -> let i' = i * 2 f = Bytes.unsafeIndex bs in (fromIntegral (f i') `shiftL` 8) .|. fromIntegral (f (i' + 1))) 切换到顺序计算会使您的速度降低x2倍,但是由于我们正在尝试对其进行优化,因此我们需要一直进行并行计算。

不要从一个非常漂亮的Repa库中窃取所有雷声,我还要展示一下所有新massiv库如何工作:

Repa.computeUnboxedS

仅提供一些具体的数字来显示实际的优化方法,这是简化的标准基准:

import Data.Massiv.Array as Massiv

bytesToMassiv :: Bytes.ByteString -> Massiv.Array Massiv.U Massiv.Ix1 Int16
bytesToMassiv bs =
  Massiv.makeArrayR U Par (Bytes.length bs `div` 2)
    (\i ->
       let i' = i * 2
           f = Bytes.unsafeIndex bs
        in (fromIntegral (f i') `shiftL` 8) .|. fromIntegral (f (i' + 1)))