random: Pass algorithm and state together as php_random_algo_with_state (#13350)

* random: Remove `php_random_status`

Since 162e1dce98, the `php_random_status` struct
contains just a single `void*`, resulting in needless indirection when
accessing the engine state and thus decreasing readability because of the
additional non-meaningful `->state` references / the local helper variables.

There is also a small, but measurable performance benefit:

    <?php
    $e = new Random\Engine\Xoshiro256StarStar(0);
    $r = new Random\Randomizer($e);

    for ($i = 0; $i < 15; $i++)
    	var_dump(strlen($r->getBytes(100000000)));

goes from roughly 3.85s down to 3.60s.

The names of the `status` variables have not yet been touched to keep the diff
small. They will be renamed to the more appropriate `state` in a follow-up
cleanup commit.

* Introduce `php_random_algo_with_state`
This commit is contained in:
Tim Düsterhus 2024-02-25 20:48:58 +01:00 committed by GitHub
parent d024dd3abd
commit 79133df156
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 211 additions and 171 deletions

View file

@ -33,12 +33,15 @@ static inline void randomizer_common_init(php_random_randomizer *randomizer, zen
php_random_engine *engine = php_random_engine_from_obj(engine_object);
/* Copy engine pointers */
randomizer->algo = engine->algo;
randomizer->status = engine->status;
randomizer->engine = engine->engine;
} else {
/* Self allocation */
randomizer->status = php_random_status_alloc(&php_random_algo_user, false);
php_random_status_state_user *state = randomizer->status->state;
php_random_status_state_user *state = php_random_status_alloc(&php_random_algo_user, false);
randomizer->engine = (php_random_algo_with_state){
.algo = &php_random_algo_user,
.status = state,
};
zend_string *mname;
zend_function *generate_method;
@ -50,9 +53,6 @@ static inline void randomizer_common_init(php_random_randomizer *randomizer, zen
state->object = engine_object;
state->generate_method = generate_method;
/* Copy common pointers */
randomizer->algo = &php_random_algo_user;
/* Mark self-allocated for memory management */
randomizer->is_userland_algo = true;
}
@ -93,6 +93,8 @@ PHP_METHOD(Random_Randomizer, __construct)
PHP_METHOD(Random_Randomizer, nextFloat)
{
php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
php_random_algo_with_state engine = randomizer->engine;
uint64_t result;
size_t total_size;
@ -101,7 +103,7 @@ PHP_METHOD(Random_Randomizer, nextFloat)
result = 0;
total_size = 0;
do {
php_random_result r = randomizer->algo->generate(randomizer->status);
php_random_result r = engine.algo->generate(engine.status);
result = result | (r.result << (total_size * 8));
total_size += r.size;
if (EG(exception)) {
@ -169,28 +171,28 @@ PHP_METHOD(Random_Randomizer, getFloat)
RETURN_THROWS();
}
RETURN_DOUBLE(php_random_gammasection_closed_open(randomizer->algo, randomizer->status, min, max));
RETURN_DOUBLE(php_random_gammasection_closed_open(randomizer->engine, min, max));
case 'C' + sizeof("ClosedClosed") - 1:
if (UNEXPECTED(max < min)) {
zend_argument_value_error(2, "must be greater than or equal to argument #1 ($min)");
RETURN_THROWS();
}
RETURN_DOUBLE(php_random_gammasection_closed_closed(randomizer->algo, randomizer->status, min, max));
RETURN_DOUBLE(php_random_gammasection_closed_closed(randomizer->engine, min, max));
case 'O' + sizeof("OpenClosed") - 1:
if (UNEXPECTED(max <= min)) {
zend_argument_value_error(2, "must be greater than argument #1 ($min)");
RETURN_THROWS();
}
RETURN_DOUBLE(php_random_gammasection_open_closed(randomizer->algo, randomizer->status, min, max));
RETURN_DOUBLE(php_random_gammasection_open_closed(randomizer->engine, min, max));
case 'O' + sizeof("OpenOpen") - 1:
if (UNEXPECTED(max <= min)) {
zend_argument_value_error(2, "must be greater than argument #1 ($min)");
RETURN_THROWS();
}
RETVAL_DOUBLE(php_random_gammasection_open_open(randomizer->algo, randomizer->status, min, max));
RETVAL_DOUBLE(php_random_gammasection_open_open(randomizer->engine, min, max));
if (UNEXPECTED(isnan(Z_DVAL_P(return_value)))) {
zend_value_error("The given interval is empty, there are no floats between argument #1 ($min) and argument #2 ($max).");
@ -208,10 +210,11 @@ PHP_METHOD(Random_Randomizer, getFloat)
PHP_METHOD(Random_Randomizer, nextInt)
{
php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
php_random_algo_with_state engine = randomizer->engine;
ZEND_PARSE_PARAMETERS_NONE();
php_random_result result = randomizer->algo->generate(randomizer->status);
php_random_result result = engine.algo->generate(engine.status);
if (EG(exception)) {
RETURN_THROWS();
}
@ -228,6 +231,8 @@ PHP_METHOD(Random_Randomizer, nextInt)
PHP_METHOD(Random_Randomizer, getInt)
{
php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
php_random_algo_with_state engine = randomizer->engine;
uint64_t result;
zend_long min, max;
@ -242,10 +247,10 @@ PHP_METHOD(Random_Randomizer, getInt)
}
if (UNEXPECTED(
randomizer->algo->range == php_random_algo_mt19937.range
&& ((php_random_status_state_mt19937 *) randomizer->status->state)->mode != MT_RAND_MT19937
engine.algo->range == php_random_algo_mt19937.range
&& ((php_random_status_state_mt19937 *) engine.status)->mode != MT_RAND_MT19937
)) {
uint64_t r = php_random_algo_mt19937.generate(randomizer->status).result >> 1;
uint64_t r = php_random_algo_mt19937.generate(engine.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.
@ -254,7 +259,7 @@ PHP_METHOD(Random_Randomizer, getInt)
result = (zend_long) (offset + min);
} else {
result = randomizer->algo->range(randomizer->status, min, max);
result = engine.algo->range(engine.status, min, max);
}
if (EG(exception)) {
@ -269,6 +274,8 @@ PHP_METHOD(Random_Randomizer, getInt)
PHP_METHOD(Random_Randomizer, getBytes)
{
php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
php_random_algo_with_state engine = randomizer->engine;
zend_string *retval;
zend_long length;
size_t total_size = 0;
@ -285,7 +292,7 @@ PHP_METHOD(Random_Randomizer, getBytes)
retval = zend_string_alloc(length, 0);
while (total_size < length) {
php_random_result result = randomizer->algo->generate(randomizer->status);
php_random_result result = engine.algo->generate(engine.status);
if (EG(exception)) {
zend_string_free(retval);
RETURN_THROWS();
@ -314,7 +321,7 @@ PHP_METHOD(Random_Randomizer, shuffleArray)
ZEND_PARSE_PARAMETERS_END();
ZVAL_DUP(return_value, array);
if (!php_array_data_shuffle(randomizer->algo, randomizer->status, return_value)) {
if (!php_array_data_shuffle(randomizer->engine, return_value)) {
RETURN_THROWS();
}
}
@ -335,7 +342,7 @@ PHP_METHOD(Random_Randomizer, shuffleBytes)
}
RETVAL_STRINGL(ZSTR_VAL(bytes), ZSTR_LEN(bytes));
if (!php_binary_string_shuffle(randomizer->algo, randomizer->status, Z_STRVAL_P(return_value), (zend_long) Z_STRLEN_P(return_value))) {
if (!php_binary_string_shuffle(randomizer->engine, Z_STRVAL_P(return_value), (zend_long) Z_STRLEN_P(return_value))) {
RETURN_THROWS();
}
}
@ -354,8 +361,7 @@ PHP_METHOD(Random_Randomizer, pickArrayKeys)
ZEND_PARSE_PARAMETERS_END();
if (!php_array_pick_keys(
randomizer->algo,
randomizer->status,
randomizer->engine,
input,
num_req,
return_value,
@ -377,6 +383,8 @@ PHP_METHOD(Random_Randomizer, pickArrayKeys)
PHP_METHOD(Random_Randomizer, getBytesFromString)
{
php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
php_random_algo_with_state engine = randomizer->engine;
zend_long length;
zend_string *source, *retval;
size_t total_size = 0;
@ -403,7 +411,7 @@ PHP_METHOD(Random_Randomizer, getBytesFromString)
if (max_offset > 0xff) {
while (total_size < length) {
uint64_t offset = randomizer->algo->range(randomizer->status, 0, max_offset);
uint64_t offset = engine.algo->range(engine.status, 0, max_offset);
if (EG(exception)) {
zend_string_free(retval);
@ -424,7 +432,7 @@ PHP_METHOD(Random_Randomizer, getBytesFromString)
int failures = 0;
while (total_size < length) {
php_random_result result = randomizer->algo->generate(randomizer->status);
php_random_result result = engine.algo->generate(engine.status);
if (EG(exception)) {
zend_string_free(retval);
RETURN_THROWS();