Sudah 7 tahun sejak pertanyaan ini diajukan, dan sepertinya masih belum ada yang menemukan solusi yang baik untuk masalah ini. Repa tidak memiliki fungsi mapM/ traverselike, bahkan yang dapat berjalan tanpa paralelisasi. Terlebih lagi, mengingat jumlah kemajuan yang terjadi dalam beberapa tahun terakhir, tampaknya hal itu juga tidak mungkin terjadi.
Karena keadaan basi dari banyak perpustakaan array di Haskell dan ketidakpuasan saya secara keseluruhan dengan set fitur mereka, saya telah mengajukan beberapa tahun pekerjaan ke dalam perpustakaan array massiv, yang meminjam beberapa konsep dari Repa, tetapi membawanya ke tingkat yang sama sekali berbeda. Cukup dengan intro.
Sebelum hari ini, ada tiga peta monadik seperti fungsi dalam massiv(tidak termasuk sinonim seperti fungsi: imapM, forM. Et al):
mapM- pemetaan biasa secara sembarangan Monad. Tidak dapat diparalelkan karena alasan yang jelas dan juga agak lambat (seperti biasanya di mapMatas daftar lambat)
traversePrim- Di sini kami dibatasi PrimMonad, yang secara signifikan lebih cepat daripada mapM, tetapi alasan untuk ini tidak penting untuk diskusi ini.
mapIO- yang ini, seperti namanya, dibatasi untuk IO(atau lebih tepatnya MonadUnliftIO, tapi itu tidak relevan). Karena kita berada di dalam, IOkita dapat secara otomatis membagi array dalam banyak potongan karena ada inti dan menggunakan utas pekerja terpisah untuk memetakan IOtindakan atas setiap elemen dalam potongan tersebut. Tidak seperti murni fmap, yang juga dapat diparalelkan, kami harus berada di IOsini karena penjadwalan yang tidak ditentukan dikombinasikan dengan efek samping dari tindakan pemetaan kami.
Jadi, begitu saya membaca pertanyaan ini, saya berpikir bahwa masalahnya secara praktis sudah diselesaikan massiv, tetapi tidak secepat itu. Generator nomor acak, seperti in mwc-randomdan lainnya di random-futidak dapat menggunakan generator yang sama di banyak utas. Artinya, satu-satunya bagian dari teka-teki yang saya lewatkan adalah: "menggambar benih acak baru untuk setiap utas yang muncul dan melanjutkan seperti biasa". Dengan kata lain, saya membutuhkan dua hal:
- Sebuah fungsi yang akan menginisialisasi sebanyak mungkin generator karena akan ada thread pekerja
- dan abstraksi yang secara mulus akan memberikan generator yang benar ke fungsi pemetaan, bergantung pada utas tempat tindakan dijalankan.
Jadi itulah yang saya lakukan.
Pertama saya akan memberikan contoh penggunaan yang dibuat khusus randomArrayWS dan initWorkerStatesfungsinya, karena lebih relevan dengan pertanyaan dan kemudian pindah ke peta monadik yang lebih umum. Berikut adalah jenis tanda tangan mereka:
randomArrayWS ::
(Mutable r ix e, MonadUnliftIO m, PrimMonad m)
=> WorkerStates g
-> Sz ix
-> (g -> m e)
-> m (Array r ix e)
initWorkerStates :: MonadIO m => Comp -> (WorkerId -> m s) -> m (WorkerStates s)
Bagi yang belum terbiasa massiv , Compargumennya adalah strategi komputasi yang akan digunakan, konstruktor terkenal adalah:
Seq - menjalankan komputasi secara berurutan, tanpa membagi utas apa pun
Par - putar utas sebanyak mungkin dan gunakan utas itu untuk melakukan pekerjaan.
Saya akan menggunakan mwc-random paket sebagai contoh pada awalnya dan kemudian pindah ke RVarT:
λ> import Data.Massiv.Array
λ> import System.Random.MWC (createSystemRandom, uniformR)
λ> import System.Random.MWC.Distributions (standard)
λ> gens <- initWorkerStates Par (\_ -> createSystemRandom)
Di atas kami menginisialisasi generator terpisah per utas menggunakan keacakan sistem, tetapi kami dapat juga menggunakan benih per utas unik dengan mengambilnya dari WorkerId argumen, yang hanya merupakan Intindeks pekerja. Dan sekarang kita dapat menggunakan generator tersebut untuk membuat array dengan nilai acak:
λ> randomArrayWS gens (Sz2 2 3) standard :: IO (Array P Ix2 Double)
Array P Par (Sz (2 :. 3))
[ [ -0.9066144845415213, 0.5264323240310042, -1.320943607597422 ]
, [ -0.6837929005619592, -0.3041255565826211, 6.53353089112833e-2 ]
]
Dengan menggunakan Parstrategi, schedulerpustaka akan membagi pekerjaan generasi secara merata di antara pekerja yang tersedia dan setiap pekerja akan menggunakan generatornya sendiri, sehingga membuatnya aman untuk thread. Tidak ada yang mencegah kami untuk menggunakan kembali hal yang samaWorkerStates jumlah acak yang selama itu tidak dilakukan secara bersamaan, yang jika tidak akan mengakibatkan pengecualian:
λ> randomArrayWS gens (Sz1 10) (uniformR (0, 9)) :: IO (Array P Ix1 Int)
Array P Par (Sz1 10)
[ 3, 6, 1, 2, 1, 7, 6, 0, 8, 8 ]
Sekarang mengesampingkan mwc-randomkita dapat menggunakan kembali konsep yang sama untuk kasus penggunaan lain yang mungkin dengan menggunakan fungsi sepertigenerateArrayWS :
generateArrayWS ::
(Mutable r ix e, MonadUnliftIO m, PrimMonad m)
=> WorkerStates s
-> Sz ix
-> (ix -> s -> m e)
-> m (Array r ix e)
dan mapWS :
mapWS ::
(Source r' ix a, Mutable r ix b, MonadUnliftIO m, PrimMonad m)
=> WorkerStates s
-> (a -> s -> m b)
-> Array r' ix a
-> m (Array r ix b)
Berikut adalah contoh yang dijanjikan tentang cara menggunakan fungsionalitas ini dengan rvar, random-fudan mersenne-random-pure64pustaka. Kita juga bisa menggunakannya di randomArrayWSsini, tetapi sebagai contoh katakanlah kita sudah memiliki array dengan RVarTs yang berbeda , dalam hal ini kita membutuhkan mapWS:
λ> import Data.Massiv.Array
λ> import Control.Scheduler (WorkerId(..), initWorkerStates)
λ> import Data.IORef
λ> import System.Random.Mersenne.Pure64 as MT
λ> import Data.RVar as RVar
λ> import Data.Random as Fu
λ> rvarArray = makeArrayR D Par (Sz2 3 9) (\ (i :. j) -> Fu.uniformT i j)
λ> mtState <- initWorkerStates Par (newIORef . MT.pureMT . fromIntegral . getWorkerId)
λ> mapWS mtState RVar.runRVarT rvarArray :: IO (Array P Ix2 Int)
Array P Par (Sz (3 :. 9))
[ [ 0, 1, 2, 2, 2, 4, 5, 0, 3 ]
, [ 1, 1, 1, 2, 3, 2, 6, 6, 2 ]
, [ 0, 1, 2, 3, 4, 4, 6, 7, 7 ]
]
Penting untuk dicatat, bahwa meskipun implementasi murni dari Mersenne Twister digunakan dalam contoh di atas, kami tidak dapat lepas dari IO. Ini karena penjadwalan non-deterministik, yang berarti kita tidak pernah tahu pekerja mana yang akan menangani bagian mana dari larik dan akibatnya generator mana yang akan digunakan untuk bagian larik mana. Di sisi atas, jika generator itu murni dan dapat dipisahkan, seperti splitmix, maka kita dapat menggunakan fungsi pembangkitan yang murni, deterministik, dan dapat diparalelkan :, randomArraytetapi itu sudah menjadi cerita yang terpisah.