এই প্রশ্নটি জিজ্ঞাসা করার পরে 7 বছর হয়ে গেছে এবং এখনও মনে হচ্ছে কেউ এই সমস্যার ভাল সমাধান নিয়ে আসে নি। রেপাতে কোনও mapM/ traverseমত ফাংশন নেই, এমনও একটি যা সমান্তরালতা ছাড়াই চলতে পারে। তদ্ব্যতীত, গত কয়েক বছরে যে পরিমাণ অগ্রগতি হয়েছিল তা বিবেচনা করে তা সম্ভবত হয় না বলেই মনে হয়।
হাস্কেলের অনেক অ্যারে লাইব্রেরির বাসি অবস্থা এবং তাদের বৈশিষ্ট্যগুলির সাথে আমার সামগ্রিক অসন্তোষের কারণে আমি কয়েক বছরের কাজ একটি অ্যারে লাইব্রেরিতে রেখেছি massiv, যা রেপা থেকে কিছু ধারণা ধার নিয়েছে, তবে একে একে সম্পূর্ণ ভিন্ন স্তরে নিয়ে গেছে। ইন্ট্রো দিয়ে যথেষ্ট।
আজ এর আগে সেখানে ফাংশন মত তিন পরমাণুসদৃশ্য মানচিত্র ছিল massiv(ফাংশন মত সমার্থক গণনা না: imapM, forM। এট):
mapM- একটি স্বেচ্ছায় সাধারণ ম্যাপিং Monad। সুস্পষ্ট কারণে সমান্তরালযোগ্য নয় এবং কিছুটা ধীর ( mapMএকটি তালিকার ধীরে ধীরে ধীরে ধীরে)
traversePrim- এখানে আমরা সীমাবদ্ধ PrimMonad, যা তুলনায় উল্লেখযোগ্যভাবে দ্রুত mapM, তবে এর কারণ এই আলোচনার জন্য গুরুত্বপূর্ণ নয়।
mapIO- এই নামটি যেমন প্রস্তাব করে, তেমন সীমাবদ্ধ IO(বা বরং MonadUnliftIOএটি অপ্রাসঙ্গিক)। যেহেতু আমরা আছি তাই আমরা IOস্বয়ংক্রিয়ভাবে অনেকগুলি খণ্ডে অ্যারে বিভক্ত করতে পারি যেহেতু কোর রয়েছে এবং IOসেই অংশগুলির প্রতিটি উপাদানগুলির ক্রিয়াটি মানচিত্র করতে পৃথক কর্মী থ্রেড ব্যবহার করতে পারে । খাঁটি থেকে পৃথক fmap, যা সমান্তরালও, IOআমাদের ম্যাপিং ক্রিয়াটির পার্শ্ব প্রতিক্রিয়াগুলির সাথে মিলিত সময় নির্ধারণের অ-নির্ধারকতার কারণে আমাদের এখানে থাকতে হবে ।
সুতরাং, আমি একবার এই প্রশ্নটি পড়ার পরে, আমি নিজেকে ভেবেছিলাম যে সমস্যাটি কার্যত সমাধান হয়েছে massiv, তবে এত দ্রুত নয় no এলোমেলো সংখ্যা জেনারেটর, যেমন mwc-randomএবং অন্যান্যরা random-fuঅনেক থ্রেডে একই জেনারেটর ব্যবহার করতে পারে না। যার অর্থ, আমি যে ধাঁধাটি হারিয়েছিলাম তার একমাত্র অংশটি হ'ল: "প্রতিটি থ্রেডের জন্য একটি নতুন এলোমেলো বীজ অঙ্কন করা এবং যথারীতি অগ্রসর হওয়া"। অন্য কথায়, আমার দুটি জিনিস প্রয়োজন:
- এমন একটি ফাংশন যা শ্রমিকের থ্রেড হিসাবে যত বেশি জেনারেটর সূচনা করবে
- এবং এমন একটি বিমূর্ততা যা কোনও থ্রেডে ক্রিয়া চলছে তার উপর নির্ভর করে ম্যাপিং ফাংশনে নির্বিঘ্নে সঠিক জেনারেটরটি দেবে।
আমি ঠিক তাই করেছি।
প্রথমে আমি বিশেষভাবে তৈরি করা randomArrayWSএবং initWorkerStatesফাংশনগুলি ব্যবহার করে উদাহরণ দেব , কারণ তারা প্রশ্নের সাথে আরও প্রাসঙ্গিক এবং পরে আরও সাধারণ মোনাডিক মানচিত্রে চলে যায় move এখানে তাদের ধরণের স্বাক্ষর রয়েছে:
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)
যাঁদের সাথে পরিচিত নন 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
-> (ix -> s -> m e)
-> 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)
-> Array r' ix a
-> 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 ]
]
এটি লক্ষণীয় গুরুত্বপূর্ণ যে উপরোক্ত উদাহরণে মার্সেন টুইস্টারকে খাঁটি প্রয়োগ করা হচ্ছে তবুও আমরা আইও থেকে বাঁচতে পারি না। এটি অ-নিরঙ্কুশিক সময়সূচির কারণে, যার অর্থ আমরা কখনই জানি না যে কোন শ্রমিক কোন অ্যারে পরিচালনা করবে এবং ফলস্বরূপ অ্যারের কোন অংশের জন্য কোন জেনারেটর ব্যবহার করা হবে। উপরের দিকে, যদি জেনারেটর বিশুদ্ধ এবং বিভাজনযোগ্য হয়, যেমন splitmix, তবে আমরা খাঁটি, নির্জনবাদী এবং সমান্তরাল প্রজন্মের ফাংশনটি ব্যবহার করতে পারি: randomArrayতবে এটি ইতিমধ্যে একটি পৃথক গল্প।