This is an old revision of the document!
PHP RFC: Object scoped RNG Implementations.
- Version: 1.1
- Date: 2021-01-17
- Author: Go Kudo zeriyoshi@gmail.com
- Status: Under Discussion
- Implementation: https://github.com/php/php-src/pull/6568
- First Published at: https://wiki.php.net/rfc/object_scope_prng
Introduction
PHP currently provides the mt_srand() and mt_rand() functions based on the Meresenne Twister as PRNGs. However, since these functions keep their state in global space, unintended function calls may cause inconsistency even for the same seed value.
mt_srand(1234); foo(); mt_rand() === 411284887; // false function foo() { mt_rand(); // code added }
This is inappropriate for applications that require consistency in the generated values (game logic, test code, etc.). These global states also affect the forked child processes. In fact, the EasySwoole Swoole extension based framework has been alerted to this behavior. [5]
In other languages, RNGs are implemented as objects, so this problem doesn't exists. [3] [4]
The global state of MT is also used by other functions that use random numbers, and this problem is further exacerbated when the consistency of the results is required by seeding with specific values. (Of course, you are right that such usage is a bad example.)
mt_srand(1234); $arr = [1, 2, 3, 4, 5]; shuffle($arr); echo print_r($arr, true); // This result is always consistent. /* Array ( [0] => 3 [1] => 2 [2] => 5 [3] => 4 [4] => 1 ) */
Therefore, it may be a good idea to consider deprecating global state-dependent RNG functions in the future.
One currently possible userland solution is to implement the PRNG in pure PHP. There is actually a userland library [1], but it is not fast enough for PHP at the moment, including with JIT (Benchmark results are available at Open Issues.)
This implementation will also allow us to support the PHP paradigm, which will become even more complex in the future.
I have created an extension for PHP to improve these [2]. This could be used to consider what specific goals this RFC is trying to achieve.
Proposal
Implements an object-scoped PRNG in PHP, providing methods equivalent to functions that use RNG results.
First, it provides the following interface.
RNG64Interface has a next64() method that returns a 64-bit wide result, but this is not necessary in a 32-bit environment. In a 64-bit environment, it is used when trying to generate a number in a range greater than 32 bits.
The next64() method throws a ValueError exception when running on a non-64bit architecture or not supported RNG class.
namespace RNG; interface RNGInterface { public function next(): int; } interface RNG64Interface extends RNGInterface { /** @throws ValueError */ public function next64(): int; }
Next, make some changes to the existing functions and add some new ones.
function shuffle(array &$array, ?RNGInterface $rng = null): bool {} function str_shuffle(string $string, ?RNGInterface $rng = null): string {} function array_rand(array $array, int $num = 1, ?RNGInterface $rng = null): int|string|array {} /** Generates a random number for the specified range using RNG. */ function rng_range(RNGInterface $rng, int $min, int $max): int [} /** Generates a sequence of bytes for a specified range using RNG. */ function rng_bytes(RNGInterface $rng, int $length): string {}
Existing RNG-specifying functions will now be able to explicitly specify the source of the RNG. This makes it clear that these functions are using the RNG internally.
Finally, we provide an RNG class that implements these interfaces. At this time, considering support for XorShift128+ , MT19937 and OSRNG.
namespace RNG; final class XorShift128Plus implements RNG64Interface {} // use XorShift128+ algorithm final class MT19937 implements RNG64Interface {} // use MT19937 algorithm final class OSRNG implements RNG64Interface {} // use php_random_bytes() internal API
Backward Incompatible Changes
With the provides of new classes, some class names (or namespaces) will no longer be available in userland.
Some of the functions that use RNGs will have additional optional $rng arguments.
- shuffle()
- str_shuffle()
- array_rand()
Proposed PHP Version(s)
8.1
RFC Impact
To SAPIs
none
To Existing Extensions
orng https://pecl.php.net/package/orng : it is a PECL extension that provides almost the same functionality. If the interface is provided by the core in the future, it will need to be supported. And that's me.
To Opcache
none
New Constants
none
php.ini Defaults
none
Open Issues
With JIT, won't the userland implementation reach a useful speed?
Comparing the speed of the userland implementation of XorShift128+ and the orng extension.
PHP 8.0
$ time php -r 'require __DIR__ . "/vendor/savvot/random/src/AbstractRand.php"; require __DIR__ . "/vendor/savvot/random/src/XorShiftRand.php"; $r = new Savvot\Random\XorShiftRand(1234); for ($i = 0; $i < 1000000; $i++) { $r->random(); }' real 0m0.441s user 0m0.429s sys 0m0.010s
PHP 8.0 + OPcache JIT
$ time php -dopcache.jit_buffer_size=100M -dopcache.enable_cli=1 -r 'require __DIR__ . "/vendor/savvot/random/src/AbstractRand.php"; require __DIR__ . "/vendor/savvot/random/src/XorShiftRand.php"; $r = new Savvot\Random\XorShiftRand(1234); for ($i = 0; $i < 1000000; $i++) { $r->random(); }' real 0m0.155s user 0m0.139s sys 0m0.015s
PHP 8.0 + orng
$ time php -r '$r = new \ORNG\XorShift128Plus(1234); for ($i = 0; $i < 1000000; $i++) { $r->next(); }' real 0m0.056s user 0m0.048s sys 0m0.008s
This provides a significant improvement, but still slow from the C implementation.
Why do we need this feature in the core and not in the extension?
In order to use the features related to pseudo-random numbers that PHP currently provides, an understanding of the core is required. If this proposal is implemented, users will be able to use pseudo-random numbers under the easy to understand concept of objects. This is a useful improvement to the overall functionality of the language.
Unaffected PHP Functionality
It does not affect any related existing functions. (However, in the case of Type II, non-destructive arguments will be added.)
- mt_srand()
- mt_rand()
- shuffle()
- str_shuffle()
- array_rand()
Proposed Voting Choices
Yes/No, requiring 2/3 majority
There are a few additional options for implementation.
Patches and Tests
- https://github.com/php/php-src/pull/6568 (Type I [outdated])