mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
random: Optimize data flow for the generate
function of native engines (#13043)
Instead of returning the generated `uint64_t` and providing the size (i.e. the number of bytes of the generated value) out-of-band via the `last_generated_size` member of the `php_random_status` struct, the `generate` function is now expected to return a new `php_random_result` struct containing both the `size` and the `result`. This has two benefits, one for the developer: It's no longer possible to forget setting `last_generated_size` to the correct value, because it now happens at the time of returning from the function. and the other benefit is for performance: The `php_random_result` struct will be returned as a register pair, thus the `size` will be directly available without reloading it from main memory. Checking a simplified version of `php_random_range64()` on Compiler Explorer (“Godbolt”) with clang 17 shows a single change in the resulting assembly showcasing the improvement (https://godbolt.org/z/G4WjdYxqx): - add rbp, qword ptr [r14] + add rbp, rdx Empirical testing confirms a measurable performance increase for the `Randomizer::getBytes()` method: <?php $e = new Random\Engine\Xoshiro256StarStar(0); $r = new Random\Randomizer($e); var_dump(strlen($r->getBytes(100000000))); goes from 250ms (before the change) to 220ms (after the change). While generating 100 MB of random data certainly is not the most common use case, it confirms the theoretical improvement in practice.
This commit is contained in:
parent
d778c24aa2
commit
162e1dce98
10 changed files with 82 additions and 64 deletions
|
@ -101,9 +101,9 @@ PHP_METHOD(Random_Randomizer, nextFloat)
|
|||
result = 0;
|
||||
total_size = 0;
|
||||
do {
|
||||
uint64_t r = randomizer->algo->generate(randomizer->status);
|
||||
result = result | (r << (total_size * 8));
|
||||
total_size += randomizer->status->last_generated_size;
|
||||
php_random_result r = randomizer->algo->generate(randomizer->status);
|
||||
result = result | (r.result << (total_size * 8));
|
||||
total_size += r.size;
|
||||
if (EG(exception)) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
@ -208,20 +208,19 @@ PHP_METHOD(Random_Randomizer, getFloat)
|
|||
PHP_METHOD(Random_Randomizer, nextInt)
|
||||
{
|
||||
php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
|
||||
uint64_t result;
|
||||
|
||||
ZEND_PARSE_PARAMETERS_NONE();
|
||||
|
||||
result = randomizer->algo->generate(randomizer->status);
|
||||
php_random_result result = randomizer->algo->generate(randomizer->status);
|
||||
if (EG(exception)) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
if (randomizer->status->last_generated_size > sizeof(zend_long)) {
|
||||
if (result.size > sizeof(zend_long)) {
|
||||
zend_throw_exception(random_ce_Random_RandomException, "Generated value exceeds size of int", 0);
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
RETURN_LONG((zend_long) (result >> 1));
|
||||
RETURN_LONG((zend_long) (result.result >> 1));
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
|
@ -246,7 +245,7 @@ PHP_METHOD(Random_Randomizer, getInt)
|
|||
randomizer->algo->range == php_random_algo_mt19937.range
|
||||
&& ((php_random_status_state_mt19937 *) randomizer->status->state)->mode != MT_RAND_MT19937
|
||||
)) {
|
||||
uint64_t r = php_random_algo_mt19937.generate(randomizer->status) >> 1;
|
||||
uint64_t r = php_random_algo_mt19937.generate(randomizer->status).result >> 1;
|
||||
|
||||
/* This is an inlined version of the RAND_RANGE_BADSCALING macro that does not invoke UB when encountering
|
||||
* (max - min) > ZEND_LONG_MAX.
|
||||
|
@ -286,13 +285,13 @@ PHP_METHOD(Random_Randomizer, getBytes)
|
|||
retval = zend_string_alloc(length, 0);
|
||||
|
||||
while (total_size < length) {
|
||||
uint64_t result = randomizer->algo->generate(randomizer->status);
|
||||
php_random_result result = randomizer->algo->generate(randomizer->status);
|
||||
if (EG(exception)) {
|
||||
zend_string_free(retval);
|
||||
RETURN_THROWS();
|
||||
}
|
||||
for (size_t i = 0; i < randomizer->status->last_generated_size; i++) {
|
||||
ZSTR_VAL(retval)[total_size++] = (result >> (i * 8)) & 0xff;
|
||||
for (size_t i = 0; i < result.size; i++) {
|
||||
ZSTR_VAL(retval)[total_size++] = (result.result >> (i * 8)) & 0xff;
|
||||
if (total_size >= length) {
|
||||
break;
|
||||
}
|
||||
|
@ -425,14 +424,14 @@ PHP_METHOD(Random_Randomizer, getBytesFromString)
|
|||
|
||||
int failures = 0;
|
||||
while (total_size < length) {
|
||||
uint64_t result = randomizer->algo->generate(randomizer->status);
|
||||
php_random_result result = randomizer->algo->generate(randomizer->status);
|
||||
if (EG(exception)) {
|
||||
zend_string_free(retval);
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < randomizer->status->last_generated_size; i++) {
|
||||
uint64_t offset = (result >> (i * 8)) & mask;
|
||||
for (size_t i = 0; i < result.size; i++) {
|
||||
uint64_t offset = (result.result >> (i * 8)) & mask;
|
||||
|
||||
if (offset > max_offset) {
|
||||
if (++failures > PHP_RANDOM_RANGE_ATTEMPTS) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue