यह प्रश्न पूछे जाने के बाद 7 साल हो गए हैं, और यह अभी भी ऐसा लगता है जैसे कोई भी इस समस्या का एक अच्छा समाधान लेकर नहीं आया है। Repa एक नहीं है mapM
/ traverse
समारोह की तरह भी एक है कि साथ में चलाना बिना चला सकता है। इसके अलावा, पिछले कुछ वर्षों में प्रगति की मात्रा को देखते हुए ऐसा लगता है कि यह संभव नहीं है।
हास्केल में कई सरणी पुस्तकालयों की बासी स्थिति और उनके फ़ीचर सेटों के साथ मेरे संपूर्ण असंतोष के कारण मैंने कुछ वर्षों के काम को एक सरणी पुस्तकालय में रखा है massiv
, जो रेपा से कुछ अवधारणाओं को उधार लेता है, लेकिन इसे पूरी तरह से अलग स्तर पर ले जाता है। इंट्रो के साथ पर्याप्त।
आज से पहले, कार्यों में massiv
(जैसे कार्यों के पर्यायवाची की गिनती नहीं :) imapM
, forM
एट अल।) की तरह तीन मानद मानचित्र थे ।
mapM
- एक मनमाना में सामान्य मानचित्रण Monad
। स्पष्ट कारणों के लिए समानांतर नहीं है और यह थोड़ा धीमा है ( mapM
किसी सूची की सामान्य रेखाओं के साथ धीमा)
traversePrim
- यहां हम प्रतिबंधित हैं PrimMonad
, जो कि इससे काफी तेज है mapM
, लेकिन इसका कारण इस चर्चा के लिए महत्वपूर्ण नहीं है।
mapIO
- यह एक, जैसा कि नाम से पता चलता है, यह प्रतिबंधित है IO
(या बल्कि MonadUnliftIO
, लेकिन यह अप्रासंगिक है)। क्योंकि हम हैं कि IO
हम स्वचालित रूप से सरणी को विभाजित कर सकते हैं जैसे कि कई कोर हैं और IO
उन टुकड़ों में प्रत्येक तत्व पर कार्रवाई को मैप करने के लिए अलग-अलग कार्यकर्ता थ्रेड्स का उपयोग करें । शुद्ध के विपरीत fmap
, जो कि समानांतर भी है, हमें IO
अपनी मैपिंग कार्रवाई के दुष्प्रभावों के साथ संयुक्त निर्धारण के गैर-निर्धारणवाद के कारण यहां होना चाहिए ।
इसलिए, एक बार जब मैंने इस प्रश्न को पढ़ा, तो मैंने खुद से सोचा कि समस्या व्यावहारिक रूप से हल हो गई है massiv
, लेकिन इतनी जल्दी नहीं। रैंडम नंबर जेनरेटर, जैसे कि mwc-random
और अन्य random-fu
कई थ्रेड में एक ही जनरेटर का उपयोग नहीं कर सकते हैं। जिसका मतलब है, पहेली का एकमात्र टुकड़ा जो मुझे याद आ रहा था: "प्रत्येक थ्रेड के लिए एक नया यादृच्छिक बीज खींचना और सामान्य रूप से आगे बढ़ना"। दूसरे शब्दों में, मुझे दो चीजों की आवश्यकता थी:
- एक ऐसा कार्य जो कई जनरेटरों को इनिशियलाइज़ करेगा, क्योंकि इसमें वर्कर थ्रेड्स होंगे
- और एक अमूर्त जो मूल रूप से मैपिंग फ़ंक्शन के लिए सही जनरेटर देगा जो उस थ्रेड पर निर्भर करता है कि कार्रवाई चल रही है।
तो ठीक यही मैंने किया।
पहले मैं विशेष रूप से तैयार किए गए randomArrayWS
और initWorkerStates
कार्यों का उपयोग करके उदाहरण दूंगा , क्योंकि वे प्रश्न के लिए अधिक प्रासंगिक हैं और बाद में अधिक सामान्य मोनडिक मानचित्र पर चले जाते हैं। यहां उनके प्रकार हस्ताक्षर हैं:
randomArrayWS ::
(Mutable r ix e, MonadUnliftIO m, PrimMonad m)
=> WorkerStates g -- ^ Use `initWorkerStates` to initialize you per thread generators
-> Sz ix -- ^ Resulting size of the array
-> (g -> m e) -- ^ Generate the value using the per thread generator.
-> m (Array r ix e)
initWorkerStates :: MonadIO m => Comp -> (WorkerId -> m s) -> m (WorkerStates s)
जो लोग परिचित नहीं हैं massiv
, उनके लिए Comp
तर्क का उपयोग करने के लिए एक संगणना रणनीति है, उल्लेखनीय निर्माता हैं:
Seq
क्रमिक रूप से चलाने के लिए, किसी भी धागे को फोर्क किए बिना
Par
- उतने ही धागे उतारे जितने में क्षमताएं हों और काम करने के लिए उनका इस्तेमाल करें।
मैं mwc-random
शुरुआत में एक उदाहरण के रूप में पैकेज का उपयोग करूंगा और बाद में आगे बढ़ूंगा RVarT
:
λ> import Data.Massiv.Array
λ> import System.Random.MWC (createSystemRandom, uniformR)
λ> import System.Random.MWC.Distributions (standard)
λ> gens <- initWorkerStates Par (\_ -> createSystemRandom)
ऊपर हमने सिस्टम यादृच्छिकता का उपयोग करते हुए एक अलग जनरेटर प्रति थ्रेड को इनिशियलाइज़ किया, लेकिन हम इसे WorkerId
तर्क से प्राप्त करके एक थ्रेड प्रति अद्वितीय बीज का उपयोग कर सकते थे , जो Int
कि कार्यकर्ता का एक मात्र सूचकांक है। और अब हम उन जनरेटर का उपयोग यादृच्छिक मूल्यों के साथ एक सरणी बनाने के लिए कर सकते हैं:
λ> 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 ]
]
Par
रणनीति का उपयोग करके scheduler
पुस्तकालय समान रूप से उपलब्ध श्रमिकों के बीच पीढ़ी के काम को विभाजित करेगा और प्रत्येक कार्यकर्ता अपने स्वयं के जनरेटर का उपयोग करेगा, इस प्रकार यह धागे को सुरक्षित बनाता है। WorkerStates
जब तक यह समवर्ती रूप से नहीं किया जाता है, तब तक कोई भी हमें एक ही तरह की मनमानी संख्या का पुन: उपयोग करने से रोकता है , अन्यथा इसका परिणाम होगा:
λ> 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 ]
अब mwc-random
पक्ष में रखते हुए हम अन्य संभावित उपयोग मामलों के लिए समान अवधारणा का पुन: उपयोग कर सकते हैं जैसे कार्यों का उपयोग करके generateArrayWS
:
generateArrayWS ::
(Mutable r ix e, MonadUnliftIO m, PrimMonad m)
=> WorkerStates s
-> Sz ix -- ^ size of new array
-> (ix -> s -> m e) -- ^ element generating action
-> m (Array r ix e)
और mapWS
:
mapWS ::
(Source r' ix a, Mutable r ix b, MonadUnliftIO m, PrimMonad m)
=> WorkerStates s
-> (a -> s -> m b) -- ^ Mapping action
-> Array r' ix a -- ^ Source array
-> m (Array r ix b)
यहाँ इस वादे का उदाहरण दिया गया है कि इस कार्यक्षमता का उपयोग कैसे करें rvar
, random-fu
और mersenne-random-pure64
पुस्तकालयों के साथ। हम randomArrayWS
यहां भी इस्तेमाल कर सकते थे , लेकिन उदाहरण के लिए मान लें कि हमारे पास पहले से ही अलग-अलग RVarT
एस के साथ एक सरणी है , जिस स्थिति में हमें इसकी आवश्यकता है 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 ]
]
यह ध्यान रखना महत्वपूर्ण है, कि इस तथ्य के बावजूद कि उपरोक्त उदाहरण में मेरसेन ट्विस्टर के शुद्ध कार्यान्वयन का उपयोग किया जा रहा है, हम IO से बच नहीं सकते। यह गैर-नियतात्मक समय-निर्धारण के कारण है, जिसका अर्थ है कि हम कभी भी यह नहीं जानते हैं कि कौन सा कार्यकर्ता किस सार को संभाल रहा है और परिणामस्वरूप सरणी के किस भाग के लिए किस जनरेटर का उपयोग किया जाएगा। ऊपर की तरफ, यदि जनरेटर शुद्ध और शानदार है, जैसे कि splitmix
, तो हम शुद्ध, निर्धारक और समानांतर पीढ़ी के कार्य का उपयोग कर सकते हैं: randomArray
लेकिन वह पहले से ही एक अलग कहानी है।