Skip to content

random: Optimize Randomizer::getBytes() #15228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ext/random/php_random.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ typedef struct _php_random_status_state_user {
} php_random_status_state_user;

typedef struct _php_random_result {
const uint64_t result;
const size_t size;
uint64_t result;
size_t size;
} php_random_result;

typedef struct _php_random_algo {
Expand Down
39 changes: 37 additions & 2 deletions ext/random/randomizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include "Zend/zend_enum.h"
#include "Zend/zend_exceptions.h"
#include "zend_portability.h"

static inline void randomizer_common_init(php_random_randomizer *randomizer, zend_object *engine_object) {
if (engine_object->ce->type == ZEND_INTERNAL_CLASS) {
Expand Down Expand Up @@ -292,14 +293,48 @@ PHP_METHOD(Random_Randomizer, getBytes)
size_t length = (size_t)user_length;
retval = zend_string_alloc(length, 0);

php_random_result result;
while (total_size + 8 <= length) {
result = engine.algo->generate(engine.state);
if (EG(exception)) {
zend_string_free(retval);
RETURN_THROWS();
}

/* If the result is not 64 bits, we can't use the fast path and
* we don't attempt to use it in the future, because we don't
* expect engines to change their output size.
*
* While it would be possible to always memcpy() the entire output,
* using result.size as the length that would result in much worse
* assembly, because it will actually emit a call to memcpy()
* instead of just storing the 64 bit value at a memory offset.
*/
if (result.size != 8) {
goto non_64;
}

#ifdef WORDS_BIGENDIAN
uint64_t swapped = ZEND_BYTES_SWAP64(result.result);
memcpy(ZSTR_VAL(retval) + total_size, &swapped, 8);
#else
memcpy(ZSTR_VAL(retval) + total_size, &result.result, 8);
#endif
total_size += 8;
}

Comment on lines +315 to +325
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused, is it intended for this to run the second while loop again? Or should it skip it to return the value directly?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will enter the second loop if the requested length is not a multiple of 8 to handle the remaining 1-7 bytes.

while (total_size < length) {
php_random_result result = engine.algo->generate(engine.state);
result = engine.algo->generate(engine.state);
if (EG(exception)) {
zend_string_free(retval);
RETURN_THROWS();
}

non_64:

for (size_t i = 0; i < result.size; i++) {
ZSTR_VAL(retval)[total_size++] = (result.result >> (i * 8)) & 0xff;
ZSTR_VAL(retval)[total_size++] = result.result & 0xff;
result.result >>= 8;
if (total_size >= length) {
break;
}
Expand Down
Loading