rand() und mt_rand() – Schlacht der PHP Zufallszahlen

Im Internet gibt es viele Beiträge und Diskussionen zum Thema rand() und mt_rand(). Beides sind Funktionen die PHP bereitstellt um Zufallszahlen zu erzeugen.
mt_rand() arbeitet nach dem Prinzip des Mersenne-Twisters der „bessere Zufallszahlen“ erzeugen soll als die bisherige Implementation der rand()-Funktion. Es kursieren auch noch Angaben, dass dieser Algorithmus laut PHP-Manual bis zu vier mal schneller sein soll als rand().
In diesem Artikel beleuchte ich mit unterschiedlichen Methoden die Facetten der beiden Algorithmen. Es handelt sich dabei um eine dynamisch erzeugte Seite, sollte sich die Implementation ändern, so kann es sein, dass komplett andere Fakten vorliegen als zum Zeitpunkt als dieser Artikel geschrieben wurde.
Die Ladezeit der Seite wird im Normalfall etwas länger dauern als bei normalen Seiten.

Zunächst ein Auszug aus dem PHP-Manual:

mt_rand — Erzeugt „bessere“ Zufallszahlen
Viele Zufallszahlengeneratoren, die auf älteren libc-Versionen basieren, haben seltsame oder doch zumindest unerwartete Verhaltensweisen und sind zudem recht langsam. Standardmäßig verwendet PHP den libc-Zufallszahlengenerator mit der Funktion rand(). Die Funktion mt_rand() kann jedoch als vollwertiger Ersatz verwendet werden. Sie verwendet einen Zufallszahlengenerator mit den bekannten Charakteristika der » Mersenne Twister, die Zufallszahlen viermal schneller generiert als der durchschnittliche libc-rand()-Aufruf.

Wenn die Funktion ohne Angabe von min und/oder max aufgerufen, gibt mt_rand() eine Pseudozufallszahl zwischen 0 und mt_getrandmax() zurück. Benötigen Sie zum Beispiel eine Zufallszahl zwischen 5 und 15 (inklusive), verwenden Sie mt_rand(5, 15).

(Quelle: PHP Manual)

Und für rand():

rand — Erzeugt einen zufälligen Integerwert
Liefert eine Pseudozufallszahl zwischen min und max (inklusive), oder zwischen 0 und get_randmax() falls keine Parameter angegeben wurden. Wenn Sie z.B. einen Zufallswert zwischen 5 und 15 benötigen so wäre der Aufruf dafür rand(5, 15).

Hinweis: Auf manchen Plattformen (Windows z.B.) ist get_randmax() nur 32768. Wenn sie einen größeren Wertebereich benötigen sollten, so können Sie entweder einen größeren max-Wert übergeben oder besser die mt_rand()-Funktion anstelle von rand() einsetzen.

Hinweis: Seit PHP 4.2.0 besteht keine Notwendigkeit mehr, den Zufallsgenerator für Zahlen mit srand() oder mt_srand() zu füttern, das geschieht nun automatisch.

(Quelle: PHP Manual)

Wie man sieht, steht hier nichts mehr von einem Geschwindigkeitsvorteil. Einzig das „bessere Zufallszahlen“ scheint auf einen Unterschied zwischen den beiden Generatoren hinzuweisen. Auch scheint bei mt_rand() keine Begrenzung auf einen Wert von 32768 (2^15) vorzuliegen.

Im folgenden soll ein wenig dargestellt werden, ob Unterschiede beispielsweise in der Zufälligkeit vorliegen oder ob eine Variante einen deutlichen Geschwindigkeitsvorteil aufweisen kann.
Eine interessante Variante die Zufälligkeit visuell darzustellen fand ich auf der Seite Dog-Net.org auf der Seite kann man sehen, dass die Verwendung von rand() im Gegensatz zu mt_rand() ein deutliches Muster in einem Bild erzeugt. Demnach können keine wirklich zufälligen Zufallszahlen vorliegen.
Ich habe diese Variante auf meinen Blog portiert, kann aber keine nachvollziehbaren Muster erkennen. Falls jemand doch mal eines sieht, das Bild gleich irgendwohin abspeichern, es wird bei jedem Aufruf der Seite neu erzeugt. Falls jemand Interesse an meinem PHP-Code hat kann ich ihn gerne zukommen lassen.

Mit mt_rand():
[exec]
// Requires the GD Library
header(„Content-type: image/png“);
$im = imagecreatetruecolor(512, 512)
or die(„Cannot Initialize new GD image stream“);
$white = imagecolorallocate($im, 255, 255, 255);
for ($y=0; $y<512; $y++) { for ($x=0; $x<512; $x++) { if (mt_rand(0,1) === 1) { imagesetpixel($im, $x, $y, $white); } } } imagegif($im,"./wp-content/temp/mtrand.gif"); imagedestroy($im); [/exec]

Mit rand():
[exec]
// Requires the GD Library
header(„Content-type: image/png“);
$im = imagecreatetruecolor(512, 512)
or die(„Cannot Initialize new GD image stream“);
$white = imagecolorallocate($im, 255, 255, 255);
for ($y=0; $y<512; $y++) { for ($x=0; $x<512; $x++) { if (rand(0,1) === 1) { imagesetpixel($im, $x, $y, $white); } } } imagegif($im,"./wp-content/temp/rand.gif"); imagedestroy($im); [/exec]

Nun nachdem dieser Test keine augenscheinlichen Schwächen mehr aufweist, noch ein Geschwindigkeitstest und eine kleine Auswertung der erzeugten Nummern:
[exec]
require_once(„./wp-content/temp/graphs.php“);

// Test der Zufallsgeneratoren
$count = 500000;
$min = 1;
$max = 5;
$values_rand = array();
$stime = microtime(true);

$i = 0;

while($i < $count) { $values_rand[rand($min,$max)]++; $i++; } $etime = microtime(true); $time = $etime-$stime; echo "{$count} Zufallszahlen generiert in {$time} Sekunden mit rand();
„;

$values_mtrand = array();
$stime = microtime(true);

for($i=0; $i < $count; $i++) { $values_mtrand[mt_rand($min,$max)]++; } $etime = microtime(true); $time = $etime-$stime; echo "{$count} Zufallszahlen generiert in {$time} Sekunden mit mt_rand();
„;

$abweichung = 0;
$labels = „“;
$values = „“;
$graph = new BAR_GRAPH(„vBar“);
for($i=$min; $i <= $max; $i++) { $labels .= $i . ","; $values .= $values_rand[$i] . ","; $abweichung = $abweichung + abs(($values_rand[$i]-$count/($max-$min+1))/($count/($max-$min+1))); } $labels = substr($labels, 0, -1); $values = substr($values, 0, -1); $graph->labels = $labels;
$graph->values = $values;
$graph->showValues = 1;
$graph->barWidth = 5;
$graph->percValuesColor=“white“;
echo $graph->create();
$abweichung = $abweichung / ($max-$min+1);
$abweichung *= 100;
echo „Durchschnittliche Abweichung vom Soll: $abweichung %“;

$abweichung = 0;
$labels = „“;
$values = „“;
$graph = new BAR_GRAPH(„vBar“);
for($i=$min; $i <= $max; $i++) { $labels .= $i . ","; $values .= $values_mtrand[$i] . ","; $abweichung = $abweichung + abs(($values_mtrand[$i]-$count/($max-$min+1))/($count/($max-$min+1))); } $labels = substr($labels, 0, -1); $values = substr($values, 0, -1); $graph->labels = $labels;
$graph->values = $values;
$graph->showValues = 1;
$graph->barWidth = 5;
$graph->percValuesColor=“white“;
echo $graph->create();
$abweichung = $abweichung / ($max-$min+1);
$abweichung *= 100;
echo „Durchschnittliche Abweichung vom Soll: $abweichung %“;
[/exec]

Bei der Ausführungsgeschwindigkeit konnte ich Unregelmäßigkeiten feststellen, manchmal liegt mt_rand() vorn, manchmal rand(). Ich vermute, dass durch die relativ lange Ausführzeit ein Taskswitch vorgenommen wird und so teilweise Zeit zur Erzeugung gerechnet wird, welche effektiv gar nicht in diesem Skript verbracht wird. Daher eine zweite Variante:
[exec]
$runcount = 15000;
// Test der Zufallsgeneratoren
$count = 20;
$min = 1;
$max = 5;
$values_rand = array();

$time = 0;
for($j = 0; $j < $runcount; $j++) { $stime = microtime(true); for($i = 0; $i < $count; $i++) { rand($min,$max); } $etime = microtime(true); $time += ($etime-$stime); } $millitime = $time*1000; echo "{$runcount} mal {$count} Zufallszahlen generiert in {$millitime} Millisekunden mit rand();
„;

$time = 0;
for($j = 0; $j < $runcount; $j++) { $stime = microtime(true); for($i = 0; $i < $count; $i++) { mt_rand($min,$max); } $etime = microtime(true); $time += ($etime-$stime); } $millitime = $time*1000; echo "{$runcount} mal {$count} Zufallszahlen generiert in {$millitime} Millisekunden mit mt_rand();
„;
[/exec]

Natürlich lässt sich auch bei dieser Variante nicht ausschließen, dass ein Taskswitch auftritt, auch sind Zweifel an der Genauigkeit der Messung durchaus gerechtfertigt, da zumindest unter Windows eine Zeitmessung unter einer Millisekunde nicht ohne weiteres möglich ist, und ich gewisse Zweifel hege, dass die Zeitmessung in PHP diese Genauigkeit aufweist.
Aber es sieht meistens so aus, als würden beide Varianten in Sachen Geschwindigkeit und auch der Zufälligkeit gleich auf liegen.

Ich würde eher mt_rand() benutzen, weil hier auf jeden Fall keine Beschränkung auf dem Maximalwert liegt. Ansonsten scheint es zumindest in der PHP version 5.2.6 keine wesentlichen Unterschiede zwischen den beiden Funktionen geben.

Die Diagramme werden übrigens mit HTML-Graphs erzeugt (die Navigation befindet sich am unteren Rand der Webseite).

1 Stern2 Sterne3 Sterne4 Sterne5 Sterne (keine Bewertung bisher)
Loading...
Samstag, 4. September 2010 Computer, Internet, Software

5 Comments zu rand() und mt_rand() – Schlacht der PHP Zufallszahlen

  • S. W. sagt:

    Hi,

    so richtig zufälli ist das Ganze leider nicht. Ich geb grad aus ein paar Ergebnissen zufällig welche aus. Es fällt auf dass da immer x mal dieselbe zahl kommt. Zahlen gehen zB 0-4, Zeile 2 von 0-8 usw jetzt hab ich frad 25 x ne Null bekommen – das wäre Im Lotte sicher ein 8er mit Zusatzzahl;-) 10exp14 …
    nächstes Ergebnis 22 von 25 Reihen die Zahl 1.

    Das ist leider ziemlich unbrauchbar.

    • Arsenal sagt:

      Hmm so analysiert habe ich das bisher noch nicht, ist sicher mal einen Versuch wert was da bei mir so rauskommt – mal schauen wann ich dazu komme. Danke fürs berichten. Auf was für einem System hast du das getestet? Zufallsfunktionen sind durchaus auch vom verwendetem System abhängig.

  • S. W. sagt:

    Ich hab das auf nem Linux-Server ausprobiert.
    Mittlerweile schaut’s besser aus – ich hatte am Anfang mt_srand(time()) – das hab ich rausgenommen, aber das darf ja eigentlich nicht geschadet haben. Wenn mir das Ergebnis nicht passt würfel ich halt nochmal;-)

    http://www.suspekt.org/2008/08/17/mt_srand-and-not-so-random-numbers/

    • Arsenal sagt:

      Solang du mt_srand() nur einmal pro Funktionsblock aufrufst sollte es in Ordnung sein. Das Problem von time() ist, dass es nur alle Sekunde einen neuen Wert kriegt, wenn du also mehrmals hintereinander mt_srand(time()) aufrufst, seedest du den Zufallsgenerator mehrmals mit der gleichen Zahl, und weil es halt nur ein Pseudozufallszahlengenerator ist, erzeugt er dir dann immer die gleiche Kette an Zufallszahlen. Deswegen gehen die meisten ja dazu über microtime() zu verwenden, weil sich das öfter ändert, aber auch hier gibt es nur 1 Million unterschiedliche Werte. Was im Endeffekt dann zu nur 1 Millionen unterschiedlichen Zufallszahlenketten führen würde.
      Wobei sich das Verhalten von den PHP-Versionen wohl auch in der Hinsicht deutlich verändert hat, mit 5.2.1:
      Die Mersenne-Twister-Implementation in PHP verwendet jetzt einen neuen Seeding-Algorithmus von Richard Wagner. Identische Seeds erzeugen nicht länger die selbe Sequenz von Werten, wie es in früheren Versionen der Fall war. Dieses Verhalten wird nicht als noch einmal wechselnd erwartet, aber es ist trotzdem nicht sicher, darauf bedingungslos zu vertrauen.

      Das halte ich für höchst eigenartig – Pseudozufallszahlengeneratoren sollten mit der gleichen Seed auch die gleiche Kette an Zufallszahlen produzieren.
      Mal schauen ob es da irgendwo was dazu gibt.

  • Matthias sagt:

    Guter Artikel, habe auf meinem Blog einen Artikel (http://blog.matthias-isler.ch/2013/07/echte-zufallszahlen-in-php-oder-wie-man-im-onlinecasino-gewinnt/) veröffentlicht wie man auch mit PHP echte Zufallszahlen generieren kann.

  • Kommentar abgeben

     
    profile for Arsenal on Stack Exchange, a network of free, community-driven Q&A sites

    PayPal-Spende



    • Hosted by netcup.de

    Durch die weitere Nutzung der Seite stimmst du der Verwendung von Cookies zu. Weitere Informationen

    Die Cookie-Einstellungen auf dieser Website sind auf "Cookies zulassen" eingestellt, um das beste Surferlebnis zu ermöglichen. Wenn du diese Website ohne Änderung der Cookie-Einstellungen verwendest oder auf "Akzeptieren" klickst, erklärst du sich damit einverstanden.

    Schließen