rfc:rng_extension

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
Next revision Both sides next revision
rfc:rng_extension [2021/05/31 13:17]
zeriyoshi back draft
rfc:rng_extension [2021/09/03 14:28]
zeriyoshi
Line 1: Line 1:
-====== PHP RFC: Add Random class ====== +====== PHP RFC: Random Extension 3.0 ====== 
-  * Version: 1.0 +  * Version: 3.0 
-  * Date: 2021-05-18+  * Date: 2021-09-02
   * Author: Go Kudo <zeriyoshi@gmail.com>   * Author: Go Kudo <zeriyoshi@gmail.com>
-  * Status: Draft +  * Status: Under Discussion 
-  * Implementation: https://github.com/php/php-src/pull/7079+  * Implementation: https://github.com/php/php-src/pull/7453
   * First Published at: http://wiki.php.net/rfc/object_scope_prng   * First Published at: http://wiki.php.net/rfc/object_scope_prng
  
 ===== Introduction ===== ===== Introduction =====
-PHP is currently having problems with RNG reproducibility. 
  
-PHP'RNG has been unified into an implementation using the Mersenne twister, with the rand() and srand() functions becoming aliases for mt_rand() and mt_srand() respectively in PHP 7.1.+Currently, PHP'random number implementation suffers from several problems.
  
-Butthese functions still store the state in the global state of PHP and are not easily reproducible. Look at the following example.+The first is that there are many different implementations. Historically, the random number implementations have been separated into lcg.c, rand.c, mt_rand.c random.c respectively, and the header file dependencies are complex. 
 + 
 +Second, the pseudo-random number generator makes use of global state. If a random number is consumed at an unexpected time, the reproducibility of the result may be lost. Look at the following example.
  
 <code php> <code php>
Line 27: Line 28:
 </code> </code>
  
-As mentioned above, the reproducibility of random numbers can easily be lost if additional processing is added later.+Reproducibility of random numbers can easily be lost if additional code is added later.
  
 In addition, the fiber extension was introduced in PHP 8.1. This makes it more difficult to keep track of the execution order. However, this problem has existed since the introduced of Generator. In addition, the fiber extension was introduced in PHP 8.1. This makes it more difficult to keep track of the execution order. However, this problem has existed since the introduced of Generator.
Line 43: Line 44:
  
 ===== Proposal ===== ===== Proposal =====
-Implements Random class. 
  
-This class implement to ext/standardalways bundled PHP core.+Clean up the implementationseparate out the random number related functions as Random extension, and add an object scoped API.
  
-The PHP code that represents the implementation is as follows:+All of the following functions will be moved to the newly created Random extension.
  
-<code php> +  * lcg_value() 
-const RANDOM_XORSHIFT128PLUS = 'xorshift128plus'; // 64-bit, fast, default +  * srand() 
-const RANDOM_MT19937 = 'mt19937'; // 32-bit, for backward compatibility +  * rand() 
-const RANDOM_SECURE = 'secure'; // Cryptographically-Secure PRNG +  * mt_srand() 
-const RANDOM_USER = 'user'; // userland implementation+  * mt_rand() 
 +  * random_int() 
 +  * random_bytes()
  
-class Random +At the same time, the following internal APIs will also be relocatedIf you want to use them, you can simply include ext/random/random.h.
-+
-    /** +
-     * Get registered algorithm in array. +
-     */ +
-    public static function getAlgos(): array; +
-     +
-    /** +
-     * Get algorithm information in array. +
-     * if not registered, returns null. +
-     */ +
-    public static function getAlgoInfo(string $algo): ?array;+
  
-    /** +  php_random_int_throw() 
-     Constructor. +  php_random_int_silent() 
-     if $seed is null, generating seed by php_random_bytes(), but whether to use depends to algo. +  php_combined_lcg() 
-     */ +  php_mt_srand() 
-    public function __construct(string $algo = RANDOM_XORSHIFT128PLUS, ?int $seed = null{}+  php_mt_rand() 
 +  * php_mt_rand_range() 
 +  * php_mt_rand_common() 
 +  * php_srand() 
 +  * php_rand() 
 +  * php_random_bytes() 
 +  * php_random_int()
  
-    /** +The following PHP constants will now be provided by the Random extension
-     * Returns raw generated number by RNG. +
-     * if on the 32-bit machine, then uses 64-bit RNG throw Exception. +
-     * but can implicating truncate on random.ignore_generated_size_exceeded=1 INI directive. +
-     */ +
-    public function nextInt(): int {}+
  
-    /*+  MT_RAND_MT19937 
-     Generates a number within a given range. +  MT_RAND_PHP
-     * similar random_int() function. +
-     */ +
-    public function getInt(int $min, int $max): int {}+
  
-    /** +To solve the scope problem, the following classes will be added
-     * Generates a string within a given range. +
-     * similar random_bytes() function. +
-     */ +
-    public function getBytes(int $length): string {}+
  
-    /*+  Random class 
-     Shuffling the given array items. +  Random\NumberGenrator abstract class 
-     similar shuffle(), but non-pass-by-reference. +  Random\NumberGenerator\XorShift128Plus class 
-     */ +  Random\NumberGenerator\MT19937 class 
-    public function shuffleArray(array $array): array {}+  * Random\NumberGenerator\Secure class
  
-    /** +The Random class is a utility class that provides functionality using random numbersIt provides the following methods, but does not provide an alternative to array_rand because it is too complex.
-     * Shuffling the given string characters. +
-     * similar str_shuffle(). +
-     */ +
-    public function shuffleString(string $string): string {}+
  
-    /*+  getInt() 
-     Serializing state to string if algo's supported. +  getBytes() 
-     */ +  shuffleArray() 
-    public function __serialize(): array {}+  * shuffleString()
  
-    /** +This class can be used in the following way.
-     * Unseriialize state from string in algo's supported. +
-     */ +
-    public function __unserialize(array $data): void {}+
  
-    /** +<code php> 
-     * Must override user-defined RNG class. +/functions 
-     * only uses inherits from the Random class with RANDOM_USER argument. +mt_srand(1234); 
-     *+mt_rand(); // generate random number 
-    protected function next(): int {} +mt_rand(1, 10); // generate random number in range 
-}+str_shuffle("foobar"); // shuffle string 
 +$arr = range(1, 10); 
 +shuffle($arr); // shuffle array items (pass by reference)
  
 +// object
 +$mt = new Random\NumberGenerator\MT19937(1234);
 +$mt->generate(); // generate random number 
 +$random = new Random($mt);
 +$random->getInt(1, 10); // generate random number in range
 +$random->shuffleString("foobar"); // shuffle string
 +$random->shuffleArray(range(1, 10)); // shuffle array items (pass by value)
 </code> </code>
  
-This class retrieves and uses the RNG algorithm registered in the core, based on the string passed in the constructor argument $algo.+The Random class accepts an instance that inherits from Random\NumberGenerator as a constructor argument.
  
-The bundled RNGs are as follows:+This class is final and cannot be cloned, but it can be serialized. 
 +This is to prevent $rng from being copied by reference to a property and causing unintended behavior.
  
-  * XorShift128+: 64-bit, reproducible, PRNG. +The serializability depends on the serializability of the contained $rng.
-  * MT19937: 32-bit, reproducible, PRNG, compatible mt_srand() / mt_rand(). +
-  * secure: 64-bit, non-reproducible, CSPRNG, uses php_random_bytes() internally. +
-  * user: user defined.+
  
-By default, XorShift128+ is used. It can generate 64-bit values, is used by major browsers, and is fast and reliable. On the other hand, MT19937 is retained for compatibility.+<code php> 
 +final class Random 
 +
 +    private Random\NumberGenerator $randomNumberGenerator;
  
-"secure" is practically equivalent to random_int(), but can be used to shuffle arrays and strings. Also, since it is an object, it is easy to exchange implementations.+    public function __construct(?Random\NumberGenerator $randomNumberGenerator = null{} 
 +    public function getNumberGenerator(): Random\NumberGenerator {} 
 +    public function getInt(int $minint $max): int {} 
 +    public function getBytes(int $length): string {} 
 +    public function shuffleArray(array $array): array {} 
 +    public function shuffleString(string $string): string {} 
 + 
 +    public function __serialize(): array {} 
 +    public function __unserialize(array $data): void {} 
 +
 +</code>
  
-"user" is special, and in order to use it, you need to inherit from the Random class and implement the next(): int method. This can be used to implement a generic RNG in userland, and can be used for unit tests where you want to generate a fixed number.+The Random\NumberGenerator abstract class has a single abstract method called generate(). 
  
 <code php> <code php>
 +namespace Random;
  
-class FixedNumberRandom extends Random+abstract class NumberGenerator
 { {
-    protected int $current = 0;+        abstract public function generate(): int {} 
 +
 +</code>
  
-    public function __construct() +By defining a class that extends Random\NumberGenerator, the user can use their own random number generator. With the introduction of JIT in PHP 8.0, this can generate random numbers at a realistic speed. 
-    { + 
-        parent::__construct(RANDOM_USER); +<code php> 
-    }+class UserDefinedRNG extends Random\NumberGenerator 
 +{ 
 +    protected int $current = 0;
  
-    protected function next(): int+    public function generate(): int
     {     {
         return ++$this->current;         return ++$this->current;
Line 156: Line 159:
 } }
  
-function foobar(Random $random): void {+function foobar(Random\NumberGenerator $numberGenerator): void {
     for ($i = 0; $i < 9; $i++) {     for ($i = 0; $i < 9; $i++) {
-        echo $random->nextInt();+        echo $numberGenerator->generate();
     }     }
 } }
  
-foobar(new FixedNumberRandom()); // Results: 123456789+foobar(new UserDefinedRNG()); // Results: 123456789
 </code> </code>
  
-Algorithms can be registered and unregistered using PHP's C APIand new implementations can be added via ExtensionThe following APIs are provided:+It is also useful when you want to use a random number sequence with a fixed resultsuch as in testing.
  
-<code c> +The Random class creates and uses an instance of the default random number generator, Random\NumberGenerator\XorShift128Plus, if the constructor argument is omitted.
-/* Register new RNG algo */ +
-int php_random_class_algo_register(const php_random_class_algo *algo);+
  
-/* Unregister RNG algo */ +XorShift128Plus is an efficient, high-quality algorithm used in modern browsers and other applications. This algorithm is capable of generating a wider range of random numbers in a 64-bit environment. In a 32-bit environment, the range beyond zend_long will simply be truncated. This indicates incompatibility between environments, but is acceptable for real-world use.
-void php_random_class_algo_unregister(const char *ident);+
  
-/* Find and get registered algo */ +The Random\NumberGenerator\MT19937 class, which implements the MT19937 Mersenne twister, is also provided for backward compatibility or when a higher period is required. However, a 1-bit right shift is required to obtain exactly the same result as mt_rand(), as shown below. This is due to historical reasons.
-const php_random_class_algo* php_random_class_algo_find(const zend_string *ident)+
-</code>+
  
-Also, as with MT, various alternative APIs using object scope RNGs will be provided.+<code php> 
 +$seed = 1234;
  
-<code c> +$mt = new Random\NumberGenerator\MT19937($seed); 
-/* similar php_mt_rand() */ +mt_srand($seed); 
-uint64_t php_random_class_next(php_random_class *random_class, zend_object *obj);+var_dump(mt_rand() === ($mt->generate() >> 1)); // true 
 +</code>
  
-/* similar php_mt_rand_range() */ +The following NumberGenerator class supports serialization. Secure is not serializable because it uses random_bytes internally and has no state.
-zend_long php_random_class_range(php_random_class *random_class, zend_object *obj, zend_long min, zend_long max);+
  
-/similar php_array_data_shuffle() */ +  Random\NumberGenerator\XorShift128Plus 
-void php_random_class_array_data_shuffle(php_random_class *random_class, zend_object *obj, zval *array);+  Random\NumberGenerator\MT19937 
 +  Random\NumberGenerator extends user-defined classes.
  
-/* similar php_string_shuffle() */ +Alsoa new internal API will be implemented.
-void php_random_class_string_shuffle(php_random_class *random_classzend_object *obj, char *str, zend_long len); +
-</code>+
  
-Random class can be serialized or cloned if the algorithm supports it. This is useful for storing and reusing state.+  * php_random_ng_next() 
 +  * php_random_ng_range() 
 +  * php_random_ng_array_data_shuffle() 
 +  * php_random_ng_string_shuffle()
  
-<code php> +A Stub showing these implementations can be found on the Pull-Request. It's probably easier to understand if you look at it.
-// serialize +
-$foo = new Random(); +
-for ($i = 0; $i < 10; $i++) { $foo->nextInt();+
-var_dump(unserialize(serialize($foo))->nextInt() === $foo->nextInt()); // true+
  
-// clone +  * [[https://github.com/php/php-src/blob/7a4ef6ccfbf4a2cd48a4f261f2911ebb7b057d46/ext/random/random.stub.php|random.stub.php]]
-$bar = new Random(); +
-for ($i = 0; $i < 10; $i++) { $bar->nextInt();+
-$baz = clone $bar; +
-var_dump($baz->nextInt() === $bar->nextInt()); // true +
-</code>+
  
-Using this feature, the first example can be rewritten as follows:+===== Future Scope =====
  
-<code php> +This proposal is just a first step to improve the situation of PHP's random number implementation.
-echo foo(1234, function (): void {}) PHP_EOL; // Result: 1480009472 +
-echo foo(1234, function (): void { mt_rand(); }) . PHP_EOL; // Result: 1480009472+
  
-function foo(int $seedcallable $bar): int { +If this proposal is approvedI will then propose the following changes 
-    $random = new Random(RANDOM_MT19937$seed)+ 
-    $result = $random->nextInt(); +  * Replace the state of the existing implementation with php_random_ng. 
-    $bar()+  * Changes random source to php_random_int() a shuffle()str_shuffle(), and array_rand() . 
-    $result += $random->nextInt()+  * Deprecate srand() and mt_srand() (step by step)
-    return $result; +
-+
-</code>+
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
-The following items will no longer be available: 
  
-  - "Random" class name +The code that includes the following header file needs to be changed to ext/random/random.h 
-  "RANDOM_XORSHIFT128PLUSconstant + 
-  "RANDOM_MT19937constant +  * ext/standard/lcg.h 
-  "RANDOM_SECUREconstant +  * ext/standard/rand.h 
-  "RANDOM_USERconstant+  * ext/standard/mt_rand.h 
 +  * ext/standard/random.h 
 + 
 +The following class names have been reserved and will no longer be available 
 + 
 +  * "Random" 
 +  "Random\NumberGenerator
 +  "Random\NumberGenerator\XorShift128Plus
 +  "Random\NumberGenerator\MT19937
 +  "Random\NumberGenerator\Secure"
  
 ===== Proposed PHP Version(s) ===== ===== Proposed PHP Version(s) =====
-8.1+8.2
  
 ===== RFC Impact ===== ===== RFC Impact =====
Line 247: Line 242:
  
 ==== New Constants ==== ==== New Constants ====
-  * RANDOM_XORSHIFT128PLUS +none
-  * RANDOM_MT19937 +
-  * RANDOM_SECURE +
-  * RANDOM_USER+
  
 ==== php.ini Defaults ==== ==== php.ini Defaults ====
-  * random.ignore_generated_size_exceeded=0 +none
- +
-If 1, implicit truncation will be performed when a 64-bit value is generated in a 32-bit environment. If 0, an exception will be thrown.+
  
 ===== Open Issues ===== ===== Open Issues =====
Line 269: Line 259:
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
-  * https://github.com/php/php-src/pull/7079+  * https://github.com/php/php-src/pull/7453
rfc/rng_extension.txt · Last modified: 2022/06/28 02:36 by zeriyoshi