In meiner jüngsten Arbeit mit Gibbs sampling
habe ich das sehr gut genutzt, RVar
was meiner Ansicht nach eine nahezu ideale Schnittstelle zur Erzeugung von Zufallszahlen bietet. Leider konnte ich Repa nicht verwenden, da ich keine monadischen Aktionen in Karten verwenden konnte.
Während eindeutig monadische Karten im Allgemeinen nicht parallelisiert werden können, scheint es mir RVar
mindestens ein Beispiel für eine Monade zu sein, bei der Effekte sicher parallelisiert werden können (zumindest im Prinzip; ich bin mit dem Innenleben von nicht besonders vertraut RVar
). . Ich möchte nämlich so etwas wie das Folgende schreiben:
drawClass :: Sample -> RVar Class
drawClass = ...
drawClasses :: Array U DIM1 Sample -> RVar (Array U DIM1 Class)
drawClasses samples = A.mapM drawClass samples
wo A.mapM
würde so etwas aussehen,
mapM :: ParallelMonad m => (a -> m b) -> Array r sh a -> m (Array r sh b)
Während dies eindeutig entscheidend von der Implementierung RVar
und dem zugrunde liegenden Thema abhängt RandomSource
, würde man im Prinzip denken, dass dies das Zeichnen eines neuen zufälligen Startwerts für jeden erzeugten Thread und das übliche Vorgehen beinhalten würde.
Intuitiv scheint es, dass dieselbe Idee auf einige andere Monaden verallgemeinert werden könnte.
Meine Frage lautet also: Könnte man eine Klasse ParallelMonad
von Monaden konstruieren, für die Effekte sicher parallelisiert werden können (vermutlich zumindest von bewohnt RVar
)?
Wie könnte es aussehen? Welche anderen Monaden könnten diese Klasse bewohnen? Haben andere über die Möglichkeit nachgedacht, wie dies in Repa funktionieren könnte?
Wenn diese Vorstellung von parallelen monadischen Aktionen nicht verallgemeinert werden kann, sieht jemand eine gute Möglichkeit, diese Arbeit im speziellen Fall von RVar
(wo es sehr nützlich wäre) zu machen? RVar
Parallelität aufzugeben ist ein sehr schwieriger Kompromiss.
quelle
RandomSource
spezifisch zu sein. Mein naiver Versuch, einen Samen zu zeichnen, wäre, etwas Einfaches und wahrscheinlich sehr Falsches zu tun, wie einen Vektor von Elementen (im Fall vonmwc-random
) zu zeichnen und 1 zu jedem Element zu addieren, um einen Samen für den ersten Arbeiter zu erzeugen, 2 für den zweiten Arbeiter usw. absolut unzureichend, wenn Sie eine Entropie von kryptografischer Qualität benötigen; hoffentlich gut, wenn Sie nur einen zufälligen Spaziergang brauchen.split
Funktion von System.Random möglich . Es hat den Nachteil, dass es unterschiedliche Ergebnissesplit
liefert (aufgrund der Art von, aber es funktioniert. Ich versuche jedoch, dies auf Repa-Arrays auszudehnen und habe nicht viel Glück. Haben Sie damit Fortschritte gemacht oder ist es tot? Ende?split
bietet dies eine notwendige Grundlage, aber beachten Sie den Kommentar zur Quelle für diesplit
Implementierung: "- keine statistische Grundlage dafür!". Ich neige dazu zu glauben, dass jede Methode zur Aufteilung eines PRNG eine ausnutzbare Korrelation zwischen seinen Zweigen hinterlässt, aber nicht über den statistischen Hintergrund verfügt, um dies zu beweisen. Im Hinblick auf die allgemeine Frage, ich bin nicht sicher , dassAntworten:
Es ist 7 Jahre her, seit diese Frage gestellt wurde, und es scheint immer noch, als hätte niemand eine gute Lösung für dieses Problem gefunden. Repa hat keine
mapM
/traverse
like-Funktion, auch keine , die ohne Parallelisierung ausgeführt werden könnte. Angesichts der Fortschritte, die in den letzten Jahren erzielt wurden, ist es außerdem unwahrscheinlich, dass dies auch passieren wird.Aufgrund des veralteten Zustands vieler Array-Bibliotheken in Haskell und meiner allgemeinen Unzufriedenheit mit ihren Funktionssätzen habe ich einige Jahre Arbeit in eine Array-Bibliothek gesteckt
massiv
, die einige Konzepte von Repa entlehnt, sie jedoch auf eine völlig andere Ebene bringt. Genug mit dem Intro.Vor dem heutigen Tag gab es drei monadische kartenähnliche Funktionen in
massiv
(ohne das synonym ähnliche Funktionen :imapM
,forM
et al.):mapM
- die übliche Zuordnung in einer beliebigenMonad
. Aus offensichtlichen Gründen nicht parallelisierbar und auch etwas langsam (wie üblichmapM
über eine Liste langsam)traversePrim
- hier beschränken wir uns aufPrimMonad
, was deutlich schneller ist alsmapM
, aber der Grund dafür ist für diese Diskussion nicht wichtig.mapIO
- Dieser ist, wie der Name schon sagt, beschränkt aufIO
(oder besser gesagtMonadUnliftIO
, aber das ist irrelevant). Da wir uns in befindenIO
, können wir das Array automatisch in so viele Blöcke aufteilen, wie Kerne vorhanden sind, und separate Arbeitsthreads verwenden, um dieIO
Aktion über jedes Element in diesen Blöcken abzubilden . Im Gegensatz zu purefmap
, das auch parallelisierbar ist, müssen wirIO
wegen des Nichtdeterminismus der Zeitplanung in Kombination mit den Nebenwirkungen unserer Mapping-Aktion hier sein.Nachdem ich diese Frage gelesen hatte, dachte ich mir, dass das Problem praktisch gelöst ist
massiv
, aber nicht so schnell. Zufallszahlengeneratoren wie inmwc-random
und andere inrandom-fu
können nicht denselben Generator für viele Threads verwenden. Das heißt, das einzige Teil des Puzzles, das mir fehlte, war: "Zeichnen eines neuen zufälligen Samens für jeden erzeugten Faden und Fortfahren wie gewohnt". Mit anderen Worten, ich brauchte zwei Dinge:Genau das habe ich getan.
Zuerst werde ich Beispiele geben, die die speziell gestalteten
randomArrayWS
undinitWorkerStates
Funktionen verwenden, da sie für die Frage relevanter sind, und später zur allgemeineren monadischen Karte übergehen. Hier sind ihre Typensignaturen:Für diejenigen, die nicht vertraut sind
massiv
, ist dasComp
Argument eine zu verwendende Berechnungsstrategie. Bemerkenswerte Konstruktoren sind:Seq
- Führen Sie die Berechnung nacheinander aus, ohne Threads zu verzweigenPar
- Drehen Sie so viele Threads wie möglich und verwenden Sie diese, um die Arbeit zu erledigen.Ich werde zunächst das
mwc-random
Paket als Beispiel verwenden und später zuRVarT
:Oben haben wir einen separaten Generator pro Thread mithilfe der Systemzufälligkeit initialisiert, aber wir hätten genauso gut einen eindeutigen Startwert pro Thread verwenden können, indem wir ihn aus dem
WorkerId
Argument abgeleitet haben, das lediglich einInt
Index des Workers ist. Und jetzt können wir diese Generatoren verwenden, um ein Array mit zufälligen Werten zu erstellen:Durch die Verwendung der
Par
Strategiescheduler
teilt die Bibliothek die Generierungsarbeit gleichmäßig auf die verfügbaren Mitarbeiter auf, und jeder Mitarbeiter verwendet seinen eigenen Generator, wodurch der Thread sicher wird. Nichts hindert uns daran, dieselbeWorkerStates
willkürliche Anzahl von Malen wiederzuverwenden, solange dies nicht gleichzeitig erfolgt, was sonst zu einer Ausnahme führen würde:Wenn
mwc-random
wir nun zur Seite stellen, können wir dasselbe Konzept für andere mögliche Anwendungsfälle wiederverwenden, indem wir Funktionen wiegenerateArrayWS
:und
mapWS
:Hier ist das versprochene Beispiel dafür, wie diese Funktion nutzen mit
rvar
,random-fu
undmersenne-random-pure64
Bibliotheken. Wir hätten es auchrandomArrayWS
hier verwenden können , aber zum Beispiel nehmen wir an, wir haben bereits ein Array mit verschiedenenRVarT
s. In diesem Fall benötigen wir einmapWS
:Es ist wichtig zu beachten, dass wir uns trotz der Tatsache, dass im obigen Beispiel die reine Implementierung von Mersenne Twister verwendet wird, dem IO nicht entziehen können. Dies liegt an der nicht deterministischen Planung, was bedeutet, dass wir nie wissen, welcher der Arbeiter welchen Teil des Arrays handhaben wird und folglich welcher Generator für welchen Teil des Arrays verwendet wird. Auf der anderen Seite, wenn der Generator rein und teilbar ist, wie zum Beispiel
splitmix
, dann können wir die reine, deterministische und parallelisierbare Generierungsfunktion verwenden:,randomArray
aber das ist bereits eine separate Geschichte.quelle
Es ist wahrscheinlich keine gute Idee, dies zu tun, da PRNGs von Natur aus sequentiell sind. Stattdessen möchten Sie Ihren Code möglicherweise wie folgt umstellen:
main
oder was haben Sie).quelle