Skip to content

Commit 31e2d2b

Browse files
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 1fc2ddc. Co-authored-by: Saki Takamachi <saki@php.net>
1 parent 2f27e0b commit 31e2d2b

File tree

2 files changed

+39
-4
lines changed

2 files changed

+39
-4
lines changed

ext/random/php_random.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ typedef struct _php_random_status_state_user {
8181
} php_random_status_state_user;
8282

8383
typedef struct _php_random_result {
84-
const uint64_t result;
85-
const size_t size;
84+
uint64_t result;
85+
size_t size;
8686
} php_random_result;
8787

8888
typedef struct _php_random_algo {

ext/random/randomizer.c

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
#include "Zend/zend_enum.h"
2828
#include "Zend/zend_exceptions.h"
29+
#include "zend_portability.h"
2930

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

296+
php_random_result result;
297+
while (total_size + 8 <= length) {
298+
result = engine.algo->generate(engine.state);
299+
if (EG(exception)) {
300+
zend_string_free(retval);
301+
RETURN_THROWS();
302+
}
303+
304+
/* If the result is not 64 bits, we can't use the fast path and
305+
* we don't attempt to use it in the future, because we don't
306+
* expect engines to change their output size.
307+
*
308+
* While it would be possible to always memcpy() the entire output,
309+
* using result.size as the length that would result in much worse
310+
* assembly, because it will actually emit a call to memcpy()
311+
* instead of just storing the 64 bit value at a memory offset.
312+
*/
313+
if (result.size != 8) {
314+
goto non_64;
315+
}
316+
317+
#ifdef WORDS_BIGENDIAN
318+
uint64_t swapped = ZEND_BYTES_SWAP64(result.result);
319+
memcpy(ZSTR_VAL(retval) + total_size, &swapped, 8);
320+
#else
321+
memcpy(ZSTR_VAL(retval) + total_size, &result.result, 8);
322+
#endif
323+
total_size += 8;
324+
}
325+
295326
while (total_size < length) {
296-
php_random_result result = engine.algo->generate(engine.state);
327+
result = engine.algo->generate(engine.state);
297328
if (EG(exception)) {
298329
zend_string_free(retval);
299330
RETURN_THROWS();
300331
}
332+
333+
non_64:
334+
301335
for (size_t i = 0; i < result.size; i++) {
302-
ZSTR_VAL(retval)[total_size++] = (result.result >> (i * 8)) & 0xff;
336+
ZSTR_VAL(retval)[total_size++] = result.result & 0xff;
337+
result.result >>= 8;
303338
if (total_size >= length) {
304339
break;
305340
}

0 commit comments

Comments
 (0)