php-src/ext/random/engine_user.c
Tim Düsterhus 162e1dce98
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.
2024-01-09 19:04:29 +01:00

80 lines
2.3 KiB
C

/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Go Kudo <zeriyoshi@php.net> |
+----------------------------------------------------------------------+
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "php.h"
#include "php_random.h"
static php_random_result generate(php_random_status *status)
{
php_random_status_state_user *s = status->state;
uint64_t result = 0;
size_t size;
zval retval;
zend_call_known_instance_method_with_0_params(s->generate_method, s->object, &retval);
if (EG(exception)) {
return (php_random_result){
.size = sizeof(uint64_t),
.result = 0,
};
}
size = Z_STRLEN(retval);
/* Guard for over 64-bit results */
if (size > sizeof(uint64_t)) {
size = sizeof(uint64_t);
}
if (size > 0) {
/* Endianness safe copy */
for (size_t i = 0; i < size; i++) {
result += ((uint64_t) (unsigned char) Z_STRVAL(retval)[i]) << (8 * i);
}
} else {
zend_throw_error(random_ce_Random_BrokenRandomEngineError, "A random engine must return a non-empty string");
return (php_random_result){
.size = sizeof(uint64_t),
.result = 0,
};
}
zval_ptr_dtor(&retval);
return (php_random_result){
.size = size,
.result = result,
};
}
static zend_long range(php_random_status *status, zend_long min, zend_long max)
{
return php_random_range(&php_random_algo_user, status, min, max);
}
const php_random_algo php_random_algo_user = {
sizeof(php_random_status_state_user),
NULL,
generate,
range,
NULL,
NULL,
};