数组

时间:2017-02-18 00:37:27

标签: arrays multithreading haskell asynchronous parallel-processing

我正在寻找一种方法,将函数f :: a -> IO(b)并行映射到二维数组上,同时保留合理的内存消耗。
我还希望能够提供数组索引作为函数的参数,即映射g :: Int -> Int -> a -> IO(b),如来自Data.Vector的imap,或来自Data.Map的mapWithKey。登记/> 当前的尝试(见下文)要么有可怕的内存消耗,要么在运行时抛出错误。

请注意,实际上,我感兴趣的函数的类型是h :: Int -> Int -> a -> Random b,其中Random表示来自Control.Monad.Random的一些Random monad;我使用evalRandIO将其移动到IO monad。

尝试解决方案:

假设我想将函数foo :: Int -> Int -> a -> IO(b)映射到类型a的二维数组元素上。 (此处ab是特定类型;没有隐含的通用量化。)
到目前为止,我尝试了以下方法:

  1. 包含Control.Concurrent.Async的简单列表

    import Control.Concurrent.Async(mapConcurrently)
    
    indexedArray :: [[(Int,Int,a)]]
    indexedArray = -- ...
    mappedArray = mapConcurrently (traverse (\(x,y,a) -> foo x y a)) indexedArray
    
  2. 这种方法存在的问题是内存消耗量超出了图表(例如4GB供参考) 如答案中所述,使用这种方法我只是并行评估行而不是所有元素,但这在实践中对我没有太大影响。

    1. 维修

      import qualified Data.Array.Repa as R
      import Data.Array.Repa(Z(..), (:.)(..), U, DIM2)
      
      array :: R.Array U DIM2 a
      array = -- ...
      mappedArray = R.traverse array id (\i (Z :. x :. y) -> unsafePerformIO $ foo x y (i (Z :. x :. y)))
      result = R.computeP mappedArray
      
    2. 请注意,R.traverse不是Data.Traversable(traverse)。由于Repa阵列不支持Data.Traversable(traverse),我无法以任何方式对IO操作进行排序,因此我必须使用unsafePerformIO才能使用内置的"traverse"功能。
      这种方法具有良好的性能和出色的内存消耗(约50MB供参考) 但是有一个问题,因为我一直遇到以下运行时错误:thread blocked indefinitely in an MVar operation

      3A。 Data.Vector和Control.Parallel

      基本上与使用Repa相同的方法导致相同的错误thread blocked indefinitely in an MVar operation 我再次使用unsafePerformIO作为Data.Vector向量没有可遍历的实例。

          import qualified Data.Vector as V
          import Control.Parallel.Strategies(using)
          import Data.Vector.Strategies(parVector)
      
          array :: V.Vector (V.Vector a)
          array = -- ...
          mappedArray = V.imap (\ y row -> V.imap (\x a -> unsafePerformIO $ foo x y a ) row ) `using` (parVector 1)
      

      与Repa相比,内存消耗和性能略差(参考值约为100MB),但仍具有可比性。

      3B。 Data.Vector和Control.Concurrent.Async

      正如sheyll所建议的,但是使用平面向量而不是嵌套向量。

          import qualified Data.Vector.Unboxed as V
          import qualified Data.Vector.Unboxed.Mutable as M
          import Control.Concurrent.Async(forConcurrently_)
      
          mappedFlattenedArray = do
            flattenedMArray <- V.unsafeThaw $ -- ...
            forConcurrently_ [0..w*h] (\i -> do v <- M.unsafeRead flattenedMArray i
                                                let (y,x) = quotRem i w
                                                v' <- foo x y v
                                                M.unsafeWrite flattenedMArray i v' )
            V.unsafeFreeze flattenedMArray
      

      不幸的是,这种方法(约3GB)的内存消耗非常高。我认为这是因为forConcurrently_创造了许多thunk?我不确定如何避免这个问题。

      1. Data.Array和Control.Concurrent.Async
      2. 使用可传输的Data.Array数组实例,如Alec所建议的那样:

            import qualified Data.Array.Unboxed as A
            import Control.Concurrent.Async(mapConcurrently)
        
            indexedArray :: A.Array (Int,Int) ((Int,Int),a)
            indexedArray = -- ...
            mappedArray = mapConcurrently (\((x,y),a) -> foo x y a) indexedArray
        

        再次,即使使用未装箱的阵列,内存消耗也非常高(约3GB);问题可能与方法1和3b中的问题相同,因为大量的内存消耗了很多内存。我不知道如何解决它。

        使用Repa的整体性能和内存消耗似乎比任何其他方法都要好,我也很欣赏处理二维数组并能够映射使用索引的函数的内置功能。不幸的是,大多数时候我得到上面提到的运行时错误(但并不总是!)。

        我之前评论过,foo的返回类型为IO(b)的唯一原因是非确定性。所以我原本以为我可以将输出类型更改为某个Random monad,而不是执行unsafePerformIO我可以简单地使用给定的种子执行runRandom。不幸的是,这并没有解决问题,因为我总是得到错误thread blocked indefinitely in an MVar operation

        有什么办法可以挽救方法2(Repa)来规避这个错误吗?还是有其他适用的方法? 我理解,一般来说,IO必然会破坏并行性,因为无法保证IO操作不会发生冲突,但至少对于这种用例,我认为解决方案应该是可行的。 (见:Why is there no mapM for repa arrays?

        另见以下问题:Parallel mapM on Repa arrays。但请注意,我事先并不知道我的函数foo需要多少个随机数。

3 个答案:

答案 0 :(得分:3)

您的第一种方法可能是您想要的,但不是链接列表。请注意,mapConcurrently :: Traversable t => (a -> IO b) -> t a -> IO (t b)类型允许您执行的内容filename='./trainMS_0.h5' with h5py.File(filename) as hf: data=hf.get('data') npdata=np.array(data) print npdata.shape # 100x1x50x50x50 label = hf.get('label') nplabel = np.array(label) print nplabel.shape # 100x50x50x50 caffe.set_mode_cpu() net = caffe.Net('conv.prototxt', caffe.TEST) im =npdata[20,0,:,:,:] # input is 50x50x50 im_input = im[np.newaxis, np.newaxis, :, :] net.blobs['data'].reshape(*im_input.shape) net.blobs['data'].data[...] = im_input # pick first filter output conv0 = net.blobs['conv'].data[0, 0] print("pre-surgery output mean {:.2f}".format(conv0.mean())) #output is 0 # set first filter bias to 1 net.params['conv'][1].data[0] = 1. net.forward() conv_out=net.blobs['conv'].data[0, 0]; print conv_out.shape # 50x50x50 traverse 相同,包括Array(我建议{{} 1}}超过Traversable这只是因为它更适合多个维度。)

Array

另外,请注意,您之前使用嵌套列表的方法只会并行化每个子列表的Vector - 它不会对整个事件进行并列化。

答案 1 :(得分:2)

聚会晚了几年,但是有一个图书馆可以直接满足您的需求:massiv。有一个函数imapIO,它具有类型签名(将m限制为IO时):

imapIO :: (Source r' ix a, Mutable r ix b)
  => (ix -> a -> IO b) -- ^ Index aware mapping action
  -> Array r' ix a -- ^ Source array
  -> IO (Array r ix b)

根据源数组的构造方式,此imapIO可以自动并行化或顺序运行。在下面的示例中,由于使用ParrandomR将被并行化:

λ> arr = makeArrayR D Par (Sz (5 :. 11)) $ \ (i :. j) -> (i, j)
λ> mapIO randomR arr :: IO (Array P Ix2 Int)
Array P Par (Sz (5 :. 11))
  [ [ 0, 1, 2, 0, 4, 4, 1, 7, 2, 8, 9 ]
  , [ 0, 1, 1, 1, 1, 4, 6, 2, 1, 8, 4 ]
  , [ 2, 1, 2, 3, 4, 5, 5, 3, 4, 9, 4 ]
  , [ 2, 2, 2, 3, 4, 5, 6, 7, 3, 8, 8 ]
  , [ 2, 4, 2, 3, 4, 4, 4, 4, 8, 8, 9 ]
  ]

话虽这么说,这是非常糟糕的方法,而且生成随机数数组的方法非常慢。由于一些原因:

  • 使用randomR(对evalRandIO同样适用)本质上使用一个存储在IORef中的全局随机数生成器。这种方法是线程安全的,但是由于它位于关键部分,并且多个线程将尝试同时使用它,因此并行化将不会那么有效。
  • 它在下面使用random软件包,这简直太慢了,我的意思是说splitmix或其他任何随机库的速度要慢约x250倍。

有两种更好的生成随机数的方法:

  • 如果生成器是纯且可拆分的,则可以确定地将初始生成器拆分为要用于生成随机数组的线程数,并且可以独立使用这些生成器来生成数组的一部分。有一个专门的功能massiv可以完成randomArray。 这是一个使用random包的示例:
λ> import Data.Massiv.Array
λ> import System.Random as System
λ> gen <- System.newStdGen
λ> compute $ randomArray gen System.split System.random Par (Sz2 2 3) :: Array P Ix2 Double
Array P Par (Sz (2 :. 3))
  [ [ 0.8867416334370383, 0.6217394261977418, 0.4536893479057291 ]
  , [ 0.6566602646092554, 0.6988432454700997, 0.14116451452794965 ]
  ]
  • 在频谱的另一端,有状态的,不可拆分的生成器,例如mwc-random。对于那些函数,有单独的函数randomArrayWCimapWS,它们还可以非常有效地生成具有随机值的数组,同时每个线程使用单独的随机数生成器。请参阅我对SO问题的回答:Parallel mapM on Repa arrays,该问题也与该问题相关联。

答案 2 :(得分:1)

为了获得最大性能和紧凑的内存布局,没有任何不必要的数组复制,我建议使用Data.Vector.Storable.Mutable

可以thaw / unsafeThaw任何不可变向量(例如Data.Vector.Storable)获取可变向量,它支持{{1}中定义的操作}}, 喜欢 Data.Vector.Storable.Mutableread,以及write约束的 monadic actions PrimMonad是基本的monad,如PrimMonadIO

例如,ST的签名是:

write

只需查看documentation for convertion to/from a mutable vector

这看起来令人生畏,但实际上非常简单:(PrimMonad m, Storable a) => MVector (PrimState m) a -> Int -> a -> m () 是您从MVector (PrimState m) a获得的,thaw可能是mST和{如果IOPrimState m,则sm ST s如果ReadWorldmIO参数为Int只是元素索引而a是新值。 此函数返回一个动作,其副作用是在给定位置进行原位/破坏性更新向量。

完成变异向量后,你可以freeze / unsafeFreeze来获取不可变向量, freezeunsafeFreezethawunsafeThaw相反, 例如unsafeFreeze具有类型签名:

unsafeFreeze :: (Storable a, PrimMonad m) => MVector (PrimState m) a -> m (Vector a) 

如您所见,该函数还返回带有PrimMonad约束的monadic操作,有关详细信息,请参阅the documentation of the primitive package

现在,为了实现您的目标,据我了解,您需要unsafeThaw outter 向量,然后concurrently(来自async){{1 }},unsafeThaw,应用readfoo每个元素,最后write每个内部向量,然后unsafeFreeze < em> outter 可变载体。

请注意,这也可以通过未装箱的可变IO阵列以类似的方式完成。

另请注意,我从您的问题中假设,并行性应限于外部向量,即所有行应并行完成,而不是所有行中的所有元素。