我正在寻找一种方法,将函数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
的二维数组元素上。 (此处a
和b
是特定类型;没有隐含的通用量化。)
到目前为止,我尝试了以下方法:
包含Control.Concurrent.Async的简单列表
import Control.Concurrent.Async(mapConcurrently)
indexedArray :: [[(Int,Int,a)]]
indexedArray = -- ...
mappedArray = mapConcurrently (traverse (\(x,y,a) -> foo x y a)) indexedArray
这种方法存在的问题是内存消耗量超出了图表(例如4GB供参考) 如答案中所述,使用这种方法我只是并行评估行而不是所有元素,但这在实践中对我没有太大影响。
维修
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
请注意,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?我不确定如何避免这个问题。
使用可传输的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
需要多少个随机数。
答案 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
可以自动并行化或顺序运行。在下面的示例中,由于使用Par
,randomR
将被并行化:
λ> 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
。对于那些函数,有单独的函数randomArrayWC
和imapWS
,它们还可以非常有效地生成具有随机值的数组,同时每个线程使用单独的随机数生成器。请参阅我对SO问题的回答:Parallel mapM on Repa arrays,该问题也与该问题相关联。答案 2 :(得分:1)
为了获得最大性能和紧凑的内存布局,没有任何不必要的数组复制,我建议使用Data.Vector.Storable.Mutable。
可以thaw
/ unsafeThaw
任何不可变向量(例如Data.Vector.Storable
)获取可变向量,它支持{{1}中定义的操作}}, 喜欢
Data.Vector.Storable.Mutable
和read
,以及write
约束的 monadic actions ,PrimMonad
是基本的monad,如PrimMonad
或IO
。
例如,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
可能是m
或ST
和{如果IO
为PrimState m
,则s
为m
ST s
如果ReadWorld
为m
,IO
参数为Int
只是元素索引而a
是新值。
此函数返回一个动作,其副作用是在给定位置进行原位/破坏性更新向量。
完成变异向量后,你可以freeze
/ unsafeFreeze
来获取不可变向量,
freeze
和unsafeFreeze
与thaw
和unsafeThaw
相反,
例如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
,应用read
,foo
每个元素,最后write
每个内部向量,然后unsafeFreeze
< em> outter 可变载体。
请注意,这也可以通过未装箱的可变IO阵列以类似的方式完成。
另请注意,我从您的问题中假设,并行性应限于外部向量,即所有行应并行完成,而不是所有行中的所有元素。