mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
random: Optimize Randomizer::getBytes() (#15228)
This patch greatly improves the performance for the common case of using a
64-bit engine and requesting a length that is a multiple of 8.
It does so by providing a fast path that will just `memcpy()` (which will be
optimized out) the returned uint64_t directly into the output buffer,
byteswapping it for big endian architectures.
The existing byte-wise copying logic was mostly left alone. It only received an
optimization of the shifting and masking that was previously applied to
`Randomizer::getBytesFromString()` in 1fc2ddc996
.
Co-authored-by: Saki Takamachi <saki@php.net>
This commit is contained in:
parent
2f27e0b2ab
commit
31e2d2b86c
2 changed files with 40 additions and 5 deletions
|
@ -81,8 +81,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 {
|
||||
|
|
|
@ -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) {
|
||||
|
@ -292,14 +293,48 @@ PHP_METHOD(Random_Randomizer, getBytes)
|
|||
size_t length = (size_t)user_length;
|
||||
retval = zend_string_alloc(length, 0);
|
||||
|
||||
while (total_size < length) {
|
||||
php_random_result result = engine.algo->generate(engine.state);
|
||||
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;
|
||||
}
|
||||
|
||||
while (total_size < length) {
|
||||
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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue