rfc:rng_extension
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision | ||
rfc:rng_extension [2021/06/01 13:59] – drop RANDOM_USER, add RandomNumberGenerator interface. zeriyoshi | rfc:rng_extension [2022/02/14 10:42] – update 4.0 zeriyoshi | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== PHP RFC: Add Random | + | ====== PHP RFC: Random |
- | * Version: | + | * Version: |
- | * Date: 2021-05-18 | + | * Date: 2022-02-14 |
- | * Author: Go Kudo < | + | * Author: Go Kudo < |
- | * Status: | + | * Status: |
- | * Implementation: | + | * Implementation: |
* First Published at: http:// | * First Published at: http:// | ||
===== Introduction ===== | ===== Introduction ===== | ||
- | PHP is currently having problems with RNG reproducibility. | ||
- | PHP's 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. | + | PHP implements several useful RNGs. However, they are currently only available in the global scope. |
- | But, these functions still store the state in the global state of PHP and are not easily reproducible. Look at the following example. | + | Mersenne Twister, PHP's default RNG, provides a function mt_srand() to initialize with a user-specified seed value, but the scope is global, which may cause unintended user behavior. |
- | <code php> | + | When a user executes mt_srand(), one would expect it to only affect result of mt_rand(), however, the following functions implicitly affect the result of mt_rand() |
- | echo foo(1234, function | + | |
- | echo foo(1234, function (): void { mt_rand(); }) . PHP_EOL; // Result: 1747253290 | + | |
- | function foo(int $seed, callable $bar): int { | + | * shuffle() |
- | | + | * str_shuffle() |
- | | + | * array_rand() |
- | $bar(); | + | |
- | $result += mt_rand(); | + | |
- | return $result; | + | |
- | } | + | |
- | </ | + | |
- | As mentioned above, the reproducibility of random numbers can easily be lost if additional processing is added later. | + | For example, in the following code, the result |
- | + | ||
- | 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. | + | |
- | + | ||
- | There is also the problem of functions that implicitly use the state stored in PHP's global state. | + | |
<code php> | <code php> | ||
mt_srand(1234); | mt_srand(1234); | ||
- | echo mt_rand() | + | $next = mt_rand(); |
mt_srand(1234); | mt_srand(1234); | ||
- | str_shuffle(' | + | $arr = range(0, 9); |
- | echo mt_rand() | + | shuffle($arr); |
+ | $next2 = mt_rand(); | ||
+ | |||
+ | die(" | ||
</ | </ | ||
- | ===== Proposal ===== | + | These behaviors were unintuitive |
- | Implements Random class and RandomNumberGenerator interface. | + | |
- | These class / interface implement | + | However, |
- | The PHP code that represents the implementation | + | There is also the issue of state management difficulties with Fiber, which was added in PHP 8.1. Nikita had this to say: |
- | <code php> | + | https://externals.io/message/115918# |
- | const RANDOM_XORSHIFT128PLUS = ' | + | |
- | const RANDOM_MT19937 = ' | + | |
- | const RANDOM_SECURE = ' | + | |
- | interface RandomNumberGenerator | + | In addition, the Mersenne Twister, can only generate 32-bit values. |
- | { | + | In recent years, many of the environments where PHP runs have been migrating to 64-bit platforms. |
- | /** | + | In order to generate |
- | * Generate a number. | + | |
- | */ | + | |
- | public function | + | |
- | } | + | |
- | final class Random | + | ===== Proposal ===== |
- | { | + | |
- | private ? | + | |
- | /** | + | Implement the XorShift128Plus |
- | * Get registered | + | XorShift128Plus is a fast, high-quality RNG that is proven |
- | */ | + | Many of the major hardware architectures are now 64-bit, so it makes sense to use this RNG. |
- | public static function getAlgos(): array; | + | |
- | + | ||
- | /** | + | |
- | * Get algorithm information | + | |
- | * if not registered, returns null. | + | |
- | */ | + | |
- | public static function getAlgoInfo(string $algo): ?array; | + | |
- | /** | + | In addition to the new algorithm, the following classes will be added to fix the global scope issue. |
- | * Constructor. | + | |
- | * if $seed is null, generating seed by php_random_bytes(), | + | |
- | */ | + | |
- | public function __construct(string|RandomNumberGenerator $algo = RANDOM_XORSHIFT128PLUS, | + | |
- | /** | + | |
- | | + | * class Random\NumberGenerator\MersenneTwister |
- | | + | * class Random\NumberGenerator\CombinedLCG |
- | | + | * class Random\NumberGenerator\Secure |
- | public function nextInt(): int {} | + | |
- | /** | + | These classes will hold independent RNG state and will not affect the global scope. |
- | * Generates a number within a given range. | + | |
- | * similar random_int() function. | + | |
- | */ | + | |
- | public function getInt(int $min, int $max): int {} | + | |
- | /** | + | An interface Random\NumberGenerator is also added and are implmeneted by the classes above. |
- | * Generates | + | This interface has only a single generate() method which makes it possible to switch between RNG implementations depending on the situation, |
- | * similar random_bytes() function. | + | allowing alternative implementations to be done by PHP in userland. This is useful, for example, for running tests. |
- | */ | + | |
- | public function getBytes(int $length): string {} | + | |
- | /** | + | RNGs other than XorShift128Plus are based on the RNGs currently implemented in PHP. |
- | * Shuffling | + | |
- | * similar shuffle(), but non-pass-by-reference. | + | |
- | */ | + | |
- | public function shuffleArray(array $array): array {} | + | |
- | /** | + | The Random\Randomizer class will be added to manipulate data using these RNGs. |
- | * Shuffling the given string characters. | + | |
- | * similar str_shuffle(). | + | |
- | */ | + | |
- | public function shuffleString(string $string): string {} | + | |
- | /** | + | This class provides the following methods: |
- | * Serializing state to string if algo's supported. | + | |
- | */ | + | |
- | public function __serialize(): array {} | + | |
- | /** | + | |
- | | + | |
- | | + | * getBytes(int $length): |
- | public function __unserialize(array $data): void {} | + | * shuffleArray(array $array): array [replacement for shuffle()] |
- | } | + | * shuffleString(string $string): string [replacement for str_shuffle()] |
- | </ | + | Method equivalent to array_rand() was not implemented at this time because the implementation is complex and can be easily implemented in userland if necessary. |
- | This class retrieves and uses the RNG algorithm registered in the core, based on the string passed in the constructor argument $algo. | + | Examples of these uses are as follows: |
- | + | ||
- | The bundled RNGs are as follows: | + | |
- | + | ||
- | * XorShift128+: | + | |
- | * MT19937: 32-bit, reproducible, | + | |
- | * secure: 64-bit, non-reproducible, | + | |
- | + | ||
- | 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. | + | |
- | + | ||
- | " | + | |
- | + | ||
- | This class also supports RNGs defined in userland. It can be used by passing an instance of a class that implements the RandomNumberGenerator interface provided at the same time as the first argument.This is useful for unit testing or when you want to use a fixed number. | + | |
<code php> | <code php> | ||
+ | // Use different RNGs for different environments. | ||
+ | $rng = $is_production | ||
+ | ? new Random\NumberGenerator\Secure() | ||
+ | : new Random\NumberGenerator\XorShift128Plus(1234); | ||
- | class UserDefinedRNG implements RandomNumberGenerator | + | $randomizer |
- | { | + | $randomizer->shuffleString('foobar'); |
- | protected int $current | + | |
- | + | ||
- | public function generate(): int | + | |
- | { | + | |
- | return ++$this-> | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | function foobar(Random $random): void { | + | |
- | for ($i = 0; $i < 9; $i++) { | + | |
- | | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | foobar(new Random(new UserDefinedRNG())); // Results: 123456789 | + | |
</ | </ | ||
- | Algorithms can be registered and unregistered using PHP's C API, and new implementations can be added via Extension. The following APIs are provided: | + | <code php> |
+ | // Safely migrate the existing mt_rand() state. | ||
- | <code c> | + | // before |
- | /* Register new RNG algo */ | + | mt_srand(1234, |
- | int php_random_class_algo_register(const php_random_class_algo *algo); | + | foobar(); |
+ | $result = str_shuffle(' | ||
- | /* Unregister RNG algo */ | + | // after |
- | void php_random_class_algo_unregister(const char *ident); | + | $randomizer = new Random\Randomizer(new Random\NumberGenerator\MersenneTwister(1234, |
- | + | foobar(); | |
- | /* Find and get registered algo */ | + | $result = $randomizer-> |
- | const php_random_class_algo* php_random_class_algo_find(const zend_string *ident); | + | |
</ | </ | ||
- | Also, as with MT, various alternative APIs using object scope RNGs will be provided. | + | As a side effect of this RFC, the following PHP functions have been moved to the new ext/random extension |
- | <code c> | + | This is because ext/standard/random.c reserves the name RANDOM and cannot be used by the extension. |
- | /* similar php_mt_rand() */ | + | In addition, all RNG-related implementations will be moved to the new random extension in order to standardize the RNG implementation. |
- | uint64_t php_random_class_next(php_random_class *random_class); | + | |
- | /* similar php_mt_rand_range() */ | + | |
- | zend_long php_random_class_range(php_random_class | + | |
+ | * rand() | ||
+ | | ||
+ | * mt_rand() | ||
+ | * random_int() | ||
+ | * random_bytes() | ||
- | /* similar php_array_data_shuffle() */ | + | The following internal APIs will also be moved to the ext/random extension: |
- | void php_random_class_array_data_shuffle(php_random_class *random_class, | + | |
- | /* similar php_string_shuffle() */ | + | |
- | void php_random_class_string_shuffle(php_random_class | + | |
- | </ | + | * php_combined_lcg() |
+ | | ||
+ | | ||
+ | * php_mt_rand_range() | ||
+ | * php_mt_rand_common() | ||
+ | * php_srand() | ||
+ | * php_rand() | ||
+ | * php_random_bytes() | ||
+ | * php_random_int() | ||
- | Random class can be serialized or cloned if the algorithm supports it. This is useful for storing and reusing state. | + | All of these features are available from the extension by simply including a single ext/ |
- | <code php> | + | The following header files are left in for extension compatibility. The contents all include ext/random/php_random.h. |
- | // serialize | + | |
- | $foo = new Random(); | + | |
- | for ($i = 0; $i < 10; $i++) { $foo-> | + | |
- | var_dump(unserialize(serialize($foo))-> | + | |
- | // clone | + | * ext/standard/php_lcg.h |
- | $bar = new Random(); | + | * ext/ |
- | for ($i = 0; $i < 10; $i++) { $bar-> | + | * ext/standard/php_mt_rand.h |
- | $baz = clone $bar; | + | * ext/ |
- | var_dump($baz-> | + | |
- | </code> | + | |
- | Using this feature, the first example can be rewritten as follows: | + | ===== Future Scope ===== |
- | <code php> | + | These are not within the scope of this RFC, but are worth considering in the future: |
- | echo foo(1234, function (): void {}) . PHP_EOL; // Result: 1480009472 | + | |
- | echo foo(1234, function (): void { mt_rand(); }) . PHP_EOL; // Result: 1480009472 | + | |
- | function foo(int $seed, callable $bar): int { | + | * Remove old header files for compatibility |
- | $random = new Random(RANDOM_MT19937, $seed); | + | * Deprecate lcg_value(), mt_srand(), srand() |
- | | + | |
- | $bar(); | + | |
- | $result += $random-> | + | |
- | return $result; | + | |
- | } | + | |
- | </ | + | |
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
- | The following items will no longer be available: | ||
- | - " | + | The following names have been reserved and will no longer be available |
- | | + | |
- | | + | * " |
- | | + | |
- | | + | |
+ | | ||
+ | | ||
+ | * " | ||
===== Proposed PHP Version(s) ===== | ===== Proposed PHP Version(s) ===== | ||
- | 8.1 | + | 8.2 |
===== RFC Impact ===== | ===== RFC Impact ===== | ||
Line 237: | Line 166: | ||
==== To Existing Extensions ==== | ==== To Existing Extensions ==== | ||
- | none | + | In the future, it may be necessary to change the included header files to point to ext/ |
==== To Opcache ==== | ==== To Opcache ==== | ||
Line 243: | Line 172: | ||
==== New Constants ==== | ==== New Constants ==== | ||
- | * RANDOM_XORSHIFT128PLUS | + | none |
- | * RANDOM_MT19937 | + | |
- | * RANDOM_SECURE | + | |
==== php.ini Defaults ==== | ==== php.ini Defaults ==== | ||
Line 254: | Line 181: | ||
===== Vote ===== | ===== Vote ===== | ||
- | Voting opens 2021-MM-DD and 2021-MM-DD at 00:00:00 EDT. 2/3 required to accept. | + | Voting opens 2022-MM-DD and 2021-MM-DD at 00:00:00 EDT. 2/3 required to accept. |
- | <doodle title=" | + | <doodle title=" |
* Yes | * Yes | ||
* No | * No | ||
Line 262: | Line 189: | ||
===== Patches and Tests ===== | ===== Patches and Tests ===== | ||
- | * https:// | + | * https:// |
rfc/rng_extension.txt · Last modified: 2022/08/01 16:52 by timwolla