Normalerweise verwende ich den Aufruf, wenn ich einen sequentiellen Zufallszahlengenerator in C setze
srand(time(NULL))
dann benutze
rand() mod N
um eine zufällige ganze Zahl zwischen 0 und N-1 zu erhalten. Wenn ich dies jedoch parallel mache, sind die Aufrufe zur Zeit (NULL) so nahe beieinander, dass sie genau dieselbe Nummer haben.
Ich habe versucht, einen linearen kongruenten Zufallszahlengenerator zu verwenden:
für einige ganze Zahlen und .
Ich weiß, dass die Wahl von für eine große ganze Zahl schnellen Ergebnissen führt, da der Moduloperator durch Abschneiden von Ziffern berechnet werden kann. Es fällt mir jedoch schwer, Samen zu etablieren, die zufällige Sequenzen mit einer großen parallelen Periode produzieren. Ich weiß, dass eine Periodenlänge maximal ist, wenn
- c und m sind Primzahlen zueinander
- a-1 ist durch alle Primfaktoren von m teilbar
- Wenn m ein Vielfaches von 4 ist, muss a-1 auch ein Vielfaches von 4 sein.
(Quelle: Wikipedia )
Aber wie stelle ich sicher, dass alle Zufallszahlenströme diese maximale Eigenschaft haben? Wie kann ich in Bezug auf den MPI die maximalen Perioden mit der linearen Kongruenzmethode einbeziehen rank
und size
erzeugen? Wäre es einfacher, einen Lagged Fibonacci oder Mersenne Twister zu verwenden, um längere parallele Zufallsströme zu erzeugen?
mod
die niederwertigen Bits zu greifen - wie Jonathan Dursi vorgeschlagen, sie sind viel weniger zufällig. Teilen Sie stattdessen Ihre (int) Zufallszahl durch maxint / range, um den gewünschten Bereich zu erhalten. Es kostet Sie eine Kluft, aber es ist wahrscheinlich eine billigere Option, um die Qualität Ihres Zufallszahlenstroms zu verbessern, als zu einem anderen PRNG zu wechseln.Antworten:
Der Trick besteht darin, den LCG-Stream jedes Prozesses zu verschachteln: Für Prozesse modifizieren wir die LCGp
sein
wobei und effektiv Schritte vorwärts gehen . Wir können sie schnell ableiten, indem wir den ursprünglichen LCG-Schritt erweitern:Ap Cp p
und das Muster ist , dass und ist das Ergebnis, beginnend mit der Nummer , sukzessiv durch Multiplikation und Zugabe Mal, dann durch Multiplikation , die alle .Ap≡apmodm, Cp 0 a 1 p c modm
Der letzte Schritt besteht darin, sicherzustellen, dass sich das Schritt-LCG jedes Prozesses nicht überlappt: Initialisieren Sie einfach den Prozess mit Rang mit und ein paralleles LCG mit einzelnen Perioden von ist bereit, wobei die ursprüngliche Periode und ist Anzahl der Prozesse. Wenn das modifizierte LCG jedes Prozesses gleichermaßen verwendet wird, wird die gesamte Periode parallel wiederhergestellt.p r xr N/p N p
Ich habe dies vor ungefähr sechs Monaten implementiert (vielleicht naiv), und Sie können den Code hier finden .
quelle
Es gibt ein sehr schönes Tutorial-Übersichtspapier von Katzgrabber, Zufallszahlen im wissenschaftlichen Rechnen: Eine Einführung , auf das ich Leute hinweise, die PRNGs für wissenschaftliche Berechnungen verwenden möchten. Lineare kongruente Generatoren sind schnell, aber das ist ungefähr alles, was sie für sie tun müssen. Sie haben kurze Zeiträume und können sehr leicht schief gehen. Perfekt vernünftig aussehende Kombinationen von a, c und m können zu schrecklich korrelierten Ausgaben führen, selbst wenn Sie die üblichen Anforderungen zwischen a, c und m erfüllen.
Schlimmer noch, in einem häufigen Fall, in dem m eine Zweierpotenz ist (die Mod-Operation ist also schnell), haben die Bits niedrigerer Ordnung eine viel kürzere Periode als die gesamte Sequenz. Wenn Sie also rand ()% N ausführen, Sie haben eine noch kürzere Zeit als erwartet.
Als Faustregel gilt, dass Generatoren mit verzögertem Fibbonacci, MT und WELL viel bessere Eigenschaften haben und immer noch ziemlich schnell sind.
In Bezug auf das parallele Seeding ist die Methode von Jack Poulson gut, da sie eine genau definierte Folge von Zahlen ergibt, die gleichmäßig zwischen den Prozessoren aufgeteilt sind. Wenn das keine Rolle spielt, können Sie alles Vernünftige tun, um die verschiedenen PRNGs zu setzen. Das gleiche Papier schlägt vor, dass sich viele Leute unabhängig voneinander etwas ausgedacht haben und die PID- oder MPI-Aufgabennummer mit der Zeit hashen. Die dort vorgeschlagene Formel ist
Ich habe keine besonderen Meinungen zu dieser spezifischen Implementierung, aber der allgemeine Ansatz ist sicherlich vernünftig.
quelle
Eine einfache Idee zum Verteilen eines typischen sequentiellen RNG über eine anständige Anzahl von Threads besteht darin, dass ein einzelner Thread den Startwert so schnell wie möglich vorschiebt und nur etwa jeden tausendsten Startwert in den Speicher sendet. Lassen Sie dann jeden Ihrer anderen Threads einen dieser beabstandeten Referenz-Seeds aufnehmen und die 1000 Werte in diesem Block verarbeiten, dh die 1000 Seeds im Block erneut regenerieren, ihre pseudozufälligen Draws generieren und dann die andere Verarbeitung Ihrer Aufgabe ausführen.
Dies funktioniert, weil bei RNGs, die nicht allzu viel berechnen (LCG ist sicherlich eine, aber viele andere sollten in dieser Kategorie sein), der eigentliche Engpass darin besteht, die Seeds in den Speicher zu senden (und wahrscheinlich auch die anschließende Verarbeitung). Wenn Sie ein LCG ausführen, ohne etwas in den Speicher zu senden, sollte das Ganze in CPU-Registern bleiben und extrem schnell sein. Selbst für ein komplizierteres RNG sollten Sie im L1-Cache bleiben und sehr schnell sein.
Ich habe diesen sehr einfachen Ansatz mit einer LCG verwendet, die wir aus alten Gründen beibehalten müssen. Grundsätzlich erhalten wir eine lineare Beschleunigung bis zu 4-8 Threads in einer typischen Multicore-Workstation. Aber jetzt werde ich die Methode aus Jack Poulsons Antwort ausprobieren und hoffentlich noch schneller werden :).
OTOH, ich glaube, dieser einfache Trick sollte für andere inhärent sequentielle RNGs funktionieren.
quelle
Diese Antwort kommt zu spät, aber Sie sollten sich SPRNG ansehen . Es wurde speziell für die parallele Skalierbarkeit entwickelt und unterstützt eine Handvoll Arten von PRNGs.
quelle