यह प्रश्न पूछे जाने के बाद 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लेकिन वह पहले से ही एक अलग कहानी है।