Ich habe eine Klasse namens erstellt QuickRandom
, deren Aufgabe es ist, schnell Zufallszahlen zu erzeugen. Es ist ganz einfach: Nehmen Sie einfach den alten Wert, multiplizieren Sie ihn mit a double
und nehmen Sie den Dezimalteil.
Hier ist meine QuickRandom
Klasse in ihrer Gesamtheit:
public class QuickRandom {
private double prevNum;
private double magicNumber;
public QuickRandom(double seed1, double seed2) {
if (seed1 >= 1 || seed1 < 0) throw new IllegalArgumentException("Seed 1 must be >= 0 and < 1, not " + seed1);
prevNum = seed1;
if (seed2 <= 1 || seed2 > 10) throw new IllegalArgumentException("Seed 2 must be > 1 and <= 10, not " + seed2);
magicNumber = seed2;
}
public QuickRandom() {
this(Math.random(), Math.random() * 10);
}
public double random() {
return prevNum = (prevNum*magicNumber)%1;
}
}
Und hier ist der Code, den ich geschrieben habe, um ihn zu testen:
public static void main(String[] args) {
QuickRandom qr = new QuickRandom();
/*for (int i = 0; i < 20; i ++) {
System.out.println(qr.random());
}*/
//Warm up
for (int i = 0; i < 10000000; i ++) {
Math.random();
qr.random();
System.nanoTime();
}
long oldTime;
oldTime = System.nanoTime();
for (int i = 0; i < 100000000; i ++) {
Math.random();
}
System.out.println(System.nanoTime() - oldTime);
oldTime = System.nanoTime();
for (int i = 0; i < 100000000; i ++) {
qr.random();
}
System.out.println(System.nanoTime() - oldTime);
}
Es ist ein sehr einfacher Algorithmus, der einfach das vorherige Doppel mit einem "magischen Zahlen" -Doppel multipliziert. Ich habe es ziemlich schnell zusammengeschmissen, also könnte ich es wahrscheinlich besser machen, aber seltsamerweise scheint es gut zu funktionieren.
Dies ist eine Beispielausgabe der auskommentierten Zeilen in der main
Methode:
0.612201846732229
0.5823974655091941
0.31062451498865684
0.8324473610354004
0.5907187526770246
0.38650264675748947
0.5243464344127049
0.7812828761272188
0.12417247811074805
0.1322738256858378
0.20614642573072284
0.8797579436677381
0.022122999476108518
0.2017298328387873
0.8394849894162446
0.6548917685640614
0.971667953190428
0.8602096647696964
0.8438709031160894
0.694884972852229
Hm. Ziemlich zufällig. Tatsächlich würde das für einen Zufallszahlengenerator in einem Spiel funktionieren.
Hier ist eine Beispielausgabe des nicht auskommentierten Teils:
5456313909
1427223941
Beeindruckend! Es arbeitet fast viermal schneller als Math.random
.
Ich erinnere mich, dass ich irgendwo etwas gelesen habe, das Math.random
gebraucht wurde, System.nanoTime()
und jede Menge verrücktes Modul- und Teilungsmaterial. Ist das wirklich notwendig? Mein Algorithmus arbeitet viel schneller und scheint ziemlich zufällig zu sein.
Ich habe zwei Fragen:
- Ist mein Algorithmus „gut genug“ (für, sagen wir, ein Spiel, wo wirklich zufällige Zahlen sind nicht so wichtig)?
- Warum macht
Math.random
man so viel, wenn es so aussieht, als würde eine einfache Multiplikation und das Ausschneiden der Dezimalstelle ausreichen?
quelle
new QuickRandom(0,5)
odernew QuickRandom(.5, 2)
. Diese geben beide wiederholt 0 für Ihre Nummer aus.Antworten:
Ihre
QuickRandom
Implementierung ist nicht wirklich gleichmäßig verteilt. Die Frequenzen sind im Allgemeinen bei den niedrigeren Werten höher, währendMath.random()
sie eine gleichmäßigere Verteilung aufweisen. Hier ist eine SSCCE, die das zeigt:Das durchschnittliche Ergebnis sieht folgendermaßen aus:
Wenn Sie den Test wiederholen, werden Sie feststellen, dass die QR-Verteilung abhängig von den anfänglichen Samen stark variiert, während die MR-Verteilung stabil ist. Manchmal erreicht es die gewünschte Gleichverteilung, aber mehr als oft nicht. Hier ist eines der extremeren Beispiele, es geht sogar über die Grenzen des Diagramms hinaus:
quelle
QuickRandom
. Manchmal ist es fast einheitlich, manchmal ist es viel schlimmer.Was Sie beschreiben, ist eine Art Zufallsgenerator, der als linearer Kongruenzgenerator bezeichnet wird . Der Generator arbeitet wie folgt:
Dieser Generator hat viele schöne Eigenschaften, hat aber als gute Zufallsquelle erhebliche Probleme. Der oben verlinkte Wikipedia-Artikel beschreibt einige der Stärken und Schwächen. Kurz gesagt, wenn Sie gute Zufallswerte benötigen, ist dies wahrscheinlich kein sehr guter Ansatz.
Hoffe das hilft!
quelle
Ihre Zufallszahlenfunktion ist schlecht, da sie einen zu geringen internen Status aufweist. Die von der Funktion in einem bestimmten Schritt ausgegebene Zahl hängt vollständig von der vorherigen Zahl ab. Wenn wir zum Beispiel annehmen, dass dies
magicNumber
2 ist (als Beispiel), dann ist die Sequenz:wird stark durch ähnliche Sequenzen gespiegelt:
In vielen Fällen führt dies zu merklichen Korrelationen in Ihrem Spiel. Wenn Sie beispielsweise Ihre Funktion nacheinander aufrufen, um X- und Y-Koordinaten für Objekte zu generieren, bilden die Objekte klare diagonale Muster.
Wenn Sie keinen guten Grund zu der Annahme haben, dass der Zufallszahlengenerator Ihre Anwendung verlangsamt (und dies ist SEHR unwahrscheinlich), gibt es keinen guten Grund, Ihre eigene zu schreiben.
quelle
Das eigentliche Problem dabei ist, dass das Ausgabehistogramm viel zu stark vom Ausgangswert abhängt - die meiste Zeit wird es eine nahezu gleichmäßige Ausgabe haben, aber die meiste Zeit wird eine deutlich ungleichmäßige Ausgabe vorliegen.
Inspiriert von diesem Artikel darüber, wie schlecht die
rand()
Funktion von PHP ist , habe ich einige zufällige Matrixbilder mitQuickRandom
und erstelltSystem.Random
. Dieser Lauf zeigt, wie manchmal der Samen einen schlechten Effekt haben kann (in diesem Fall zugunsten niedrigerer Zahlen), wenn diesSystem.Random
ziemlich gleichmäßig ist.QuickRandom
System.Random
Noch schlimmer
Wenn wir initialisieren,
QuickRandom
währendnew QuickRandom(0.01, 1.03)
wir dieses Bild erhalten:Der Code
quelle
magicNumber
Multiplikation eine ähnliche Zahl ergibtprevNum
, die den Mangel an Zufälligkeit zeigt. Wenn wir die Samen verwenden, erhaltennew QuickRandom(0.01, 1.03)
wir diese i.imgur.com/Q1Yunbe.png !System.Drawing.Bitmap
.Ein Problem mit Ihrem Zufallszahlengenerator ist, dass es keinen "versteckten Zustand" gibt. Wenn ich weiß, welche Zufallszahl Sie beim letzten Anruf zurückgegeben haben, kenne ich jede einzelne Zufallszahl, die Sie bis zum Ende der Zeit senden, da es nur eine gibt mögliches nächstes Ergebnis und so weiter und so fort.
Eine andere zu berücksichtigende Sache ist die "Periode" Ihres Zufallszahlengenerators. Offensichtlich kann bei einer endlichen Zustandsgröße, die dem Mantissenanteil eines Doppels entspricht, vor dem Schleifen nur höchstens 2 ^ 52 Werte zurückgegeben werden. Aber das ist im besten Fall - können Sie beweisen, dass es keine Schleifen der Periode 1, 2, 3, 4 gibt ...? Wenn dies der Fall ist, hat Ihr RNG in diesen Fällen ein schreckliches, entartetes Verhalten.
Wird Ihre Zufallszahlengenerierung außerdem für alle Startpunkte gleichmäßig verteilt sein? Wenn dies nicht der Fall ist, ist Ihr RNG voreingenommen - oder schlimmer noch, je nach Startsamen auf unterschiedliche Weise voreingenommen.
Wenn Sie all diese Fragen beantworten können, ist das großartig. Wenn Sie nicht können, dann wissen Sie, warum die meisten Leute das Rad nicht neu erfinden und einen bewährten Zufallszahlengenerator verwenden;)
(Ein gutes Sprichwort lautet übrigens: Der schnellste Code ist Code, der nicht ausgeführt wird. Sie könnten den schnellsten Zufall () der Welt erstellen, aber es ist nicht gut, wenn er nicht sehr zufällig ist.)
quelle
0 -> 0
. Je nach Samen kann es viele andere geben. (Zum Beispiel mit einem Samen von 3,0,0.5 -> 0.5
,0.25 -> 0.75 -> 0.25
,0.2 -> 0.6 -> 0.8 -> 0.4 -> 0.2
, etc.)Ein häufiger Test, den ich bei der Entwicklung von PRNGs immer durchgeführt habe, war:
Auf diese Weise konnte ich schnell Ideen wiederholen, die PRNGs für Sequenzen von etwa 1 bis 20 Megabyte "gut genug" waren. Es ergab auch ein besseres Bild von oben nach unten, als es nur mit dem Auge zu untersuchen, da jedes "gut genug" PRNG mit einem halben Wort Zustand die Fähigkeit Ihrer Augen, den Zykluspunkt zu sehen, schnell überschreiten könnte.
Wenn ich wirklich wählerisch wäre, könnte ich die guten Algorithmen verwenden und die DIEHARD / NIST-Tests für sie ausführen, um einen besseren Einblick zu erhalten, und dann zurückgehen und weitere Optimierungen vornehmen.
Der Vorteil des Komprimierungstests im Gegensatz zu einer Frequenzanalyse besteht darin, dass es trivial einfach ist, eine gute Verteilung zu erstellen: Geben Sie einfach einen Block mit 256 Längen aus, der alle Zeichen mit den Werten 0 bis 255 enthält, und führen Sie dies 100.000 Mal durch. Diese Sequenz hat jedoch einen Zyklus mit einer Länge von 256.
Eine verzerrte Verteilung, auch mit einem kleinen Rand, sollte von einem Komprimierungsalgorithmus erfasst werden, insbesondere wenn Sie genug (z. B. 1 Megabyte) der Sequenz angeben, um damit zu arbeiten. Wenn einige Zeichen, Bigramme oder n-Gramme häufiger auftreten, kann ein Komprimierungsalgorithmus diesen Verteilungsversatz in Codes codieren, die das häufige Auftreten mit kürzeren Codewörtern begünstigen, und Sie erhalten ein Delta der Komprimierung.
Da die meisten Komprimierungsalgorithmen schnell sind und keine Implementierung erfordern (da sie von Betriebssystemen nur herumliegen), ist der Komprimierungstest sehr nützlich, um ein PRNG, das Sie möglicherweise entwickeln, schnell zu bewerten.
Viel Glück bei Ihren Experimenten!
Oh, ich habe diesen Test mit dem oben genannten Rng durchgeführt und dabei den folgenden kleinen Mod Ihres Codes verwendet:
Die Ergebnisse waren:
Ich würde ein PRNG für gut halten, wenn die Ausgabedatei überhaupt nicht komprimiert werden könnte. Um ehrlich zu sein, ich dachte nicht, dass Ihr PRNG so gut abschneiden würde, nur 16% auf ~ 20 Megs sind für eine so einfache Konstruktion ziemlich beeindruckend. Aber ich halte es immer noch für einen Fehlschlag.
quelle
Der schnellste Zufallsgenerator, den Sie implementieren können, ist folgender:
XD, Witze auseinander, neben allem, was hier gesagt wird, möchte ich dazu beitragen, dass das Testen von Zufallssequenzen "eine schwierige Aufgabe" ist [1], und es gibt mehrere Tests, die bestimmte Eigenschaften von Pseudozufallszahlen überprüfen Viele davon hier: http://www.random.org/analysis/#2005
Eine einfache Möglichkeit, die "Qualität" des Zufallsgenerators zu bewerten, ist der alte Chi-Quadrat-Test.
Zitieren [1]
Verwenden Sie diese Theorie und den folgenden Code:
Ich habe folgendes Ergebnis erhalten:
Was für QuickRandom weit entfernt von r (außerhalb von
r ± 2 * sqrt(r)
) istDas heißt, QuickRandom könnte schnell sein, ist aber (wie in anderen Antworten angegeben) nicht gut als Zufallszahlengenerator
[1] SEDGEWICK ROBERT, Algorithmen in C , Addinson Wesley Publishing Company, 1990, Seiten 516 bis 518
quelle
int[]
auf Null initialisiert, sodass dieser Teil nicht benötigt wird. Casting to Float ist sinnlos, wenn Sie mit Doppel arbeiten. Zuletzt: Das Aufrufen der Methodennamen random1 und random2 ist ziemlich lustig.Ich habe ein kurzes Modell Ihres Algorithmus in JavaScript zusammengestellt, um die Ergebnisse auszuwerten. Es generiert 100.000 zufällige Ganzzahlen von 0 bis 99 und verfolgt die Instanz jeder Ganzzahl.
Das erste, was mir auffällt, ist, dass Sie eher eine niedrige als eine hohe Zahl erhalten. Sie sehen dies am meisten, wenn
seed1
es hoch undseed2
niedrig ist. In einigen Fällen habe ich nur 3 Zahlen erhalten.Bestenfalls muss Ihr Algorithmus etwas verfeinert werden.
quelle
Wenn die
Math.Random()
Funktion das Betriebssystem aufruft, um die Uhrzeit abzurufen, können Sie sie nicht mit Ihrer Funktion vergleichen. Ihre Funktion ist ein PRNG, während diese Funktion nach echten Zufallszahlen strebt. Äpfel und Orangen.Ihr PRNG ist zwar schnell, verfügt jedoch nicht über genügend Statusinformationen, um einen langen Zeitraum zu erreichen, bevor es wiederholt wird (und seine Logik ist nicht ausgefeilt genug, um auch nur die Zeiträume zu erreichen, die mit so vielen Statusinformationen möglich sind).
Punkt ist die Länge der Sequenz, bevor sich Ihr PRNG zu wiederholen beginnt. Dies geschieht, sobald die PRNG-Maschine einen Zustandsübergang in einen Zustand durchführt, der mit einem früheren Zustand identisch ist. Von dort aus werden die Übergänge wiederholt, die in diesem Zustand begonnen haben. Ein weiteres Problem bei PRNGs kann eine geringe Anzahl eindeutiger Sequenzen sowie eine entartete Konvergenz bei einer bestimmten Sequenz sein, die sich wiederholt. Es kann auch unerwünschte Muster geben. Angenommen, ein PRNG sieht ziemlich zufällig aus, wenn die Zahlen dezimal gedruckt werden. Eine Überprüfung der binären Werte zeigt jedoch, dass Bit 4 bei jedem Aufruf einfach zwischen 0 und 1 wechselt. Hoppla!
Schauen Sie sich den Mersenne Twister und andere Algorithmen an. Es gibt Möglichkeiten, ein Gleichgewicht zwischen der Periodenlänge und den CPU-Zyklen herzustellen. Ein grundlegender Ansatz (der im Mersenne Twister verwendet wird) besteht darin, im Zustandsvektor herumzulaufen. Das heißt, wenn eine Zahl erzeugt wird, basiert sie nicht auf dem gesamten Zustand, sondern nur auf einigen Wörtern aus dem Zustandsarray, die einigen Bitoperationen unterliegen. Bei jedem Schritt bewegt sich der Algorithmus jedoch auch im Array und verschlüsselt den Inhalt jeweils ein wenig.
quelle
/dev/random
ist dies eine Quelle für echte Zufälligkeit, die von Gerätetreibern erhalten wird, und kein PRNG. Es blockiert, wenn nicht genügend Bits verfügbar sind. Das Schwestergerät/dev/urandom
blockiert auch nicht, aber es ist immer noch nicht genau ein PRNG, da es mit zufälligen Bits aktualisiert wird, wenn sie verfügbar sind.nanoTime()
+ counter / hash werden für den Standard-Seedjava.util.Random
von oracle / OpenJDK verwendet. Das ist nur für den Samen, dann ist es ein Standard-LCG. Tatsächlich akzeptiert der OP-Generator 2 Zufallszahlen für Seed, was in Ordnung ist - also kein Unterschied alsjava.util.Random
.System.currentTimeMillis()
war der Standard-Startwert in JDK1.4-Es gibt viele, viele Pseudozufallszahlengeneratoren. Zum Beispiel Knuths Ranarray , der Mersenne-Twister , oder suchen Sie nach LFSR-Generatoren. Knuths monumentale "Seminumerische Algorithmen" analysieren das Gebiet und schlagen einige lineare Kongruenzgeneratoren vor (einfach zu implementieren, schnell).
Aber ich würde vorschlagen, dass Sie sich einfach an
java.util.Random
oder haltenMath.random
, sie sind schnell und zumindest für den gelegentlichen Gebrauch in Ordnung (dh Spiele und dergleichen). Wenn Sie in der Distribution nur paranoid sind (ein Monte-Carlo-Programm oder ein genetischer Algorithmus), überprüfen Sie deren Implementierung (Quelle ist irgendwo verfügbar) und geben Sie ihnen eine wirklich zufällige Zahl, entweder von Ihrem Betriebssystem oder von random.org . Wenn dies für eine Anwendung erforderlich ist, bei der die Sicherheit von entscheidender Bedeutung ist, müssen Sie sich selbst ausgraben. Und da Sie in diesem Fall nicht glauben sollten, was für ein farbiges Quadrat mit fehlenden Bits hier herausspritzt, werde ich jetzt die Klappe halten.quelle
Es ist sehr unwahrscheinlich, dass die Leistung bei der Zufallszahlengenerierung für einen von Ihnen entwickelten Anwendungsfall ein Problem darstellt, es sei denn, Sie greifen
Random
über mehrere Threads auf eine einzelne Instanz zu (weil dies der FallRandom
istsynchronized
).Wenn dies jedoch wirklich der Fall ist und Sie schnell viele Zufallszahlen benötigen, ist Ihre Lösung viel zu unzuverlässig. Manchmal liefert es gute Ergebnisse, manchmal liefert es schreckliche Ergebnisse (basierend auf den Anfangseinstellungen).
Wenn Sie die gleichen Zahlen möchten, die
Random
Ihnen die Klasse nur schneller gibt, können Sie die Synchronisation dort entfernen:Ich habe einfach den
java.util.Random
Code genommen und die Synchronisation entfernt, was zu einer doppelten Leistung im Vergleich zum Original auf meinem Oracle HotSpot JVM 7u9 führt. Es ist immer noch langsamer als IhrQuickRandom
, aber es liefert viel konsistentere Ergebnisse. Um genau zu sein, gibt es für dieselbenseed
Werte und Single-Threaded-Anwendungen dieselben Pseudozufallszahlen wie für die ursprünglicheRandom
Klasse.Dieser Code basiert auf dem aktuellen Code
java.util.Random
in OpenJDK 7u, der unter GNU GPL v2 lizenziert ist .BEARBEITEN 10 Monate später:
Ich habe gerade festgestellt, dass Sie nicht einmal meinen obigen Code verwenden müssen, um eine nicht synchronisierte
Random
Instanz zu erhalten. Es gibt auch eine im JDK!Schauen Sie sich die
ThreadLocalRandom
Klasse von Java 7 an . Der darin enthaltene Code ist fast identisch mit meinem obigen Code. Die Klasse ist einfach eine vom lokalen Thread isolierteRandom
Version, die zum schnellen Generieren von Zufallszahlen geeignet ist. Der einzige Nachteil, den ich mir vorstellen kann, ist, dass Sie ihn nichtseed
manuell einstellen können .Anwendungsbeispiel:
quelle
:)
Das ist interessant, danke!Bei 'Random' geht es nicht nur darum, Zahlen zu erhalten. Was Sie haben, ist pseudozufällig
Wenn Pseudozufällig für Ihre Zwecke gut genug ist, ist es sicher viel schneller (und XOR + Bitshift ist schneller als das, was Sie haben).
Rolf
Bearbeiten:
OK, nachdem ich in dieser Antwort zu voreilig war, möchte ich den wahren Grund beantworten, warum Ihr Code schneller ist:
Aus dem JavaDoc für Math.Random ()
Dies ist wahrscheinlich der Grund, warum Ihr Code schneller ist.
quelle
Math.random()
langsamer ist. Es müsste entwederRandom
jedes Mal synchronisiert oder neu erstellt werden, und keiner von beiden ist in Bezug auf die Leistung sehr attraktiv. Wenn ich mich um Leistung kümmern würde, würde ich meine eigene erstellennew Random
und diese einfach nutzen. : Pjava.util.Random ist nicht viel anders, eine von Knuth beschriebene grundlegende LCG. Es hat jedoch zwei Hauptvorteile / -unterschiede:
Unten ist es die Hauptroutine, die 'zufällige' Ganzzahlen in java.util.Random generiert.
Wenn Sie AtomicLong und den nicht genannten Status entfernen (dh alle Bits von verwenden
long
), erhalten Sie mehr Leistung als bei der doppelten Multiplikation / Modulo.Letzte Anmerkung:
Math.random
Sollte nur für einfache Tests verwendet werden, ist es anfällig für Konflikte, und wenn Sie sogar ein paar Threads haben, die es gleichzeitig aufrufen, verschlechtert sich die Leistung. Ein wenig bekanntes historisches Merkmal ist die Einführung von CAS in Java - um einen berüchtigten Benchmark zu übertreffen (zuerst von IBM über Intrinsics und dann von Sun über "CAS from Java").quelle
Dies ist die Zufallsfunktion, die ich für meine Spiele verwende. Es ist ziemlich schnell und hat eine gute (ausreichende) Verteilung.
quelle
volatile
der Compiler ohne diese Möglichkeit frei ist, lokale Variablen nach Belieben zu entfernen (oder einzuführen).