From 7cdadcd0e3e17a8937db7194fc8da32239979744 Mon Sep 17 00:00:00 2001 From: Emmanuel Dreyfus Date: Thu, 3 Apr 2025 02:38:24 +0200 Subject: [PATCH 1/7] On some systems, uniqid() is critically slow. If available, use uuidgen() system call instead of a loop on gettimeofday(), that improves performances lot. --- configure.ac | 1 + ext/standard/uniqid.c | 54 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 01d9ded69b920..853ffadecb750 100644 --- a/configure.ac +++ b/configure.ac @@ -588,6 +588,7 @@ AC_CHECK_FUNCS(m4_normalize([ unsetenv usleep utime + uuidgen vasprintf ])) diff --git a/ext/standard/uniqid.c b/ext/standard/uniqid.c index c0b9555ccefd0..c1899e6b0f3e9 100644 --- a/ext/standard/uniqid.c +++ b/ext/standard/uniqid.c @@ -31,10 +31,44 @@ #include #endif +#ifdef HAVE_UUIDGEN +#include +#include +#endif + #include "ext/random/php_random.h" #include "ext/random/php_random_csprng.h" -#ifdef HAVE_GETTIMEOFDAY +#ifdef HAVE_UUIDGEN +/* {{{ Generates a unique ID */ +PHP_FUNCTION(uniqid) +{ + char *prefix = ""; + bool more_entropy = 0; + zend_string *uniqid; + size_t prefix_len = 0; + struct uuid uuid; + int *n = (int *)&uuid; + + ZEND_PARSE_PARAMETERS_START(0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(prefix, prefix_len) + Z_PARAM_BOOL(more_entropy) + ZEND_PARSE_PARAMETERS_END(); + + (void)uuidgen(&uuid, 1); + + if (more_entropy) { + n[1] &= 0xffffff; + uniqid = strpprintf(0, "%s%08x%06x.%08x", prefix, n[0], n[1], n[2]); + } else { + n[1] &= 0xfffff; + uniqid = strpprintf(0, "%s%08x%05x", prefix, n[0], n[1]); + } + + RETURN_STR(uniqid); +} +#elif HAVE_GETTIMEOFDAY ZEND_TLS struct timeval prev_tv = { 0, 0 }; /* {{{ Generates a unique ID */ @@ -53,6 +87,24 @@ PHP_FUNCTION(uniqid) Z_PARAM_BOOL(more_entropy) ZEND_PARSE_PARAMETERS_END(); +#ifdef __NetBSD__ + struct uuid uuid; + int *n = (int *)&uuid; + + /* Use faster uuidgen() if available */ + (void)uuidgen(&uuid, 1); + + if (more_entropy) { + n[1] &= 0xffffff; + uniqid = strpprintf(0, "%s%08x%06x.%08x", prefix, n[0], n[1], n[2]); + } else { + n[1] &= 0xfffff; + uniqid = strpprintf(0, "%s%08x%05x", prefix, n[0], n[1]); + } + + RETURN_STR(uniqid); +#endif + /* This implementation needs current microsecond to change, * hence we poll time until it does. This is much faster than * calling usleep(1) which may cause the kernel to schedule From 18779505ea10aa210eee5939644434cc2f081278 Mon Sep 17 00:00:00 2001 From: Emmanuel Dreyfus Date: Thu, 3 Apr 2025 08:41:43 +0200 Subject: [PATCH 2/7] Use #ifdef HAVE_UUIDGEN and not #ifdef __NetBSD__ I once used before adding the uuidgen() test in configure --- ext/standard/uniqid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/standard/uniqid.c b/ext/standard/uniqid.c index c1899e6b0f3e9..966dc9cb4f38e 100644 --- a/ext/standard/uniqid.c +++ b/ext/standard/uniqid.c @@ -87,7 +87,7 @@ PHP_FUNCTION(uniqid) Z_PARAM_BOOL(more_entropy) ZEND_PARSE_PARAMETERS_END(); -#ifdef __NetBSD__ +#ifdef HAVE_UUIDGEN struct uuid uuid; int *n = (int *)&uuid; From 8e15cf4856cc2a9b0308aa503c8c625a7f0b6424 Mon Sep 17 00:00:00 2001 From: Emmanuel Dreyfus Date: Thu, 3 Apr 2025 14:24:48 +0200 Subject: [PATCH 3/7] Remove an early test that I should have removed earlier. --- ext/standard/uniqid.c | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/ext/standard/uniqid.c b/ext/standard/uniqid.c index 966dc9cb4f38e..86f8fb3b671fc 100644 --- a/ext/standard/uniqid.c +++ b/ext/standard/uniqid.c @@ -87,24 +87,6 @@ PHP_FUNCTION(uniqid) Z_PARAM_BOOL(more_entropy) ZEND_PARSE_PARAMETERS_END(); -#ifdef HAVE_UUIDGEN - struct uuid uuid; - int *n = (int *)&uuid; - - /* Use faster uuidgen() if available */ - (void)uuidgen(&uuid, 1); - - if (more_entropy) { - n[1] &= 0xffffff; - uniqid = strpprintf(0, "%s%08x%06x.%08x", prefix, n[0], n[1], n[2]); - } else { - n[1] &= 0xfffff; - uniqid = strpprintf(0, "%s%08x%05x", prefix, n[0], n[1]); - } - - RETURN_STR(uniqid); -#endif - /* This implementation needs current microsecond to change, * hence we poll time until it does. This is much faster than * calling usleep(1) which may cause the kernel to schedule From 81adbda8ae0050e85041f6070a09a57f62fc6762 Mon Sep 17 00:00:00 2001 From: Emmanuel Dreyfus Date: Thu, 3 Apr 2025 14:36:45 +0200 Subject: [PATCH 4/7] Also remove now unused variable --- ext/standard/uniqid.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/standard/uniqid.c b/ext/standard/uniqid.c index 86f8fb3b671fc..858c8d2a74b57 100644 --- a/ext/standard/uniqid.c +++ b/ext/standard/uniqid.c @@ -76,7 +76,6 @@ PHP_FUNCTION(uniqid) { char *prefix = ""; bool more_entropy = 0; - zend_string *uniqid; int sec, usec; size_t prefix_len = 0; struct timeval tv; From eb40c48a16e62fc42c4762432d8721e7362effbb Mon Sep 17 00:00:00 2001 From: Emmanuel Dreyfus Date: Thu, 3 Apr 2025 15:08:48 +0200 Subject: [PATCH 5/7] Avoid using the lower bytes of UUID since it may contain the host MAC on some platforms. Reuse the more_entropy from the gettimeofday() version instead. Refactor to avoid code duplication. --- ext/standard/uniqid.c | 52 +++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/ext/standard/uniqid.c b/ext/standard/uniqid.c index 858c8d2a74b57..c5a0b523972d5 100644 --- a/ext/standard/uniqid.c +++ b/ext/standard/uniqid.c @@ -23,32 +23,39 @@ #include #include - #include + +#ifdef HAVE_UUIDGEN +#include +#include +#else #ifdef PHP_WIN32 #include "win32/time.h" #else #include #endif -#ifdef HAVE_UUIDGEN -#include -#include +ZEND_TLS struct timeval prev_tv = { 0, 0 }; #endif #include "ext/random/php_random.h" #include "ext/random/php_random_csprng.h" -#ifdef HAVE_UUIDGEN +#if defined(HAVE_UUIDGEN) || defined(HAVE_GETTIMEOFDAY) /* {{{ Generates a unique ID */ PHP_FUNCTION(uniqid) { char *prefix = ""; bool more_entropy = 0; zend_string *uniqid; + int sec, usec; size_t prefix_len = 0; +#ifdef HAVE_UUIDGEN struct uuid uuid; int *n = (int *)&uuid; +#else + struct timeval tv; +#endif ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL @@ -56,36 +63,12 @@ PHP_FUNCTION(uniqid) Z_PARAM_BOOL(more_entropy) ZEND_PARSE_PARAMETERS_END(); +#ifdef HAVE_UUIDGEN (void)uuidgen(&uuid, 1); - if (more_entropy) { - n[1] &= 0xffffff; - uniqid = strpprintf(0, "%s%08x%06x.%08x", prefix, n[0], n[1], n[2]); - } else { - n[1] &= 0xfffff; - uniqid = strpprintf(0, "%s%08x%05x", prefix, n[0], n[1]); - } - - RETURN_STR(uniqid); -} -#elif HAVE_GETTIMEOFDAY -ZEND_TLS struct timeval prev_tv = { 0, 0 }; - -/* {{{ Generates a unique ID */ -PHP_FUNCTION(uniqid) -{ - char *prefix = ""; - bool more_entropy = 0; - int sec, usec; - size_t prefix_len = 0; - struct timeval tv; - - ZEND_PARSE_PARAMETERS_START(0, 2) - Z_PARAM_OPTIONAL - Z_PARAM_STRING(prefix, prefix_len) - Z_PARAM_BOOL(more_entropy) - ZEND_PARSE_PARAMETERS_END(); - + sec = n[0]; + usec = n[1] % 0x100000; +#else /* This implementation needs current microsecond to change, * hence we poll time until it does. This is much faster than * calling usleep(1) which may cause the kernel to schedule @@ -100,6 +83,7 @@ PHP_FUNCTION(uniqid) sec = (int) tv.tv_sec; usec = (int) (tv.tv_usec % 0x100000); +#endif /* The max value usec can have is 0xF423F, so we use only five hex * digits for usecs. @@ -118,5 +102,5 @@ PHP_FUNCTION(uniqid) RETURN_STR(uniqid); } -#endif /* }}} */ +#endif From 5471a5a9582b06d28439f5136d21c2c4fc625c4a Mon Sep 17 00:00:00 2001 From: Emmanuel Dreyfus Date: Sun, 6 Apr 2025 02:25:06 +0200 Subject: [PATCH 6/7] Another attempt to addresse the performance when the system clock misbehaves, while keeping the documented behavior of returning a time based value. Instead of looping on gettimeofday until the system time changes, call it once, and if time has not changed, return the previous value +1us. --- configure.ac | 1 - ext/standard/uniqid.c | 48 ++++++++++++++++++------------------------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/configure.ac b/configure.ac index 853ffadecb750..01d9ded69b920 100644 --- a/configure.ac +++ b/configure.ac @@ -588,7 +588,6 @@ AC_CHECK_FUNCS(m4_normalize([ unsetenv usleep utime - uuidgen vasprintf ])) diff --git a/ext/standard/uniqid.c b/ext/standard/uniqid.c index c5a0b523972d5..00b9f06f63994 100644 --- a/ext/standard/uniqid.c +++ b/ext/standard/uniqid.c @@ -23,25 +23,20 @@ #include #include -#include -#ifdef HAVE_UUIDGEN -#include -#include -#else +#include #ifdef PHP_WIN32 #include "win32/time.h" #else #include #endif -ZEND_TLS struct timeval prev_tv = { 0, 0 }; -#endif - #include "ext/random/php_random.h" #include "ext/random/php_random_csprng.h" -#if defined(HAVE_UUIDGEN) || defined(HAVE_GETTIMEOFDAY) +#ifdef HAVE_GETTIMEOFDAY +ZEND_TLS struct timeval prev_tv = { 0, 0 }; + /* {{{ Generates a unique ID */ PHP_FUNCTION(uniqid) { @@ -50,12 +45,7 @@ PHP_FUNCTION(uniqid) zend_string *uniqid; int sec, usec; size_t prefix_len = 0; -#ifdef HAVE_UUIDGEN - struct uuid uuid; - int *n = (int *)&uuid; -#else struct timeval tv; -#endif ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL @@ -63,27 +53,29 @@ PHP_FUNCTION(uniqid) Z_PARAM_BOOL(more_entropy) ZEND_PARSE_PARAMETERS_END(); -#ifdef HAVE_UUIDGEN - (void)uuidgen(&uuid, 1); - - sec = n[0]; - usec = n[1] % 0x100000; -#else /* This implementation needs current microsecond to change, - * hence we poll time until it does. This is much faster than - * calling usleep(1) which may cause the kernel to schedule - * another process, causing a pause of around 10ms. + * If system clock lags and report the same time again, + * we just return the previous value +1us. + * This is much faster than calling usleep(1) which may + * cause the kernel to schedule another process, causing + * a pause of around 10ms. */ - do { - (void)gettimeofday((struct timeval *) &tv, (struct timezone *) NULL); - } while (tv.tv_sec == prev_tv.tv_sec && tv.tv_usec == prev_tv.tv_usec); + (void)gettimeofday((struct timeval *) &tv, (struct timezone *) NULL); + if (tv.tv_sec <= prev_tv.tv_sec || + (tv.tv_sec == prev_tv.tv_sec && tv.tv_usec <= prev_tv.tv_usec)) { + tv.tv_sec = prev_tv.tv_sec; + tv.tv_usec = prev_tv.tv_usec + 1; + if (tv.tv_usec >= 1000000) { + tv.tv_sec++; + tv.tv_usec -= 1000000; + } + } prev_tv.tv_sec = tv.tv_sec; prev_tv.tv_usec = tv.tv_usec; sec = (int) tv.tv_sec; usec = (int) (tv.tv_usec % 0x100000); -#endif /* The max value usec can have is 0xF423F, so we use only five hex * digits for usecs. @@ -102,5 +94,5 @@ PHP_FUNCTION(uniqid) RETURN_STR(uniqid); } -/* }}} */ #endif +/* }}} */ From f03c2bc80fc7a361a3aa39083bc799e5c2aacc18 Mon Sep 17 00:00:00 2001 From: Emmanuel Dreyfus Date: Mon, 7 Apr 2025 17:32:34 +0200 Subject: [PATCH 7/7] < instead of <= qo that second member can be used --- ext/standard/uniqid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/standard/uniqid.c b/ext/standard/uniqid.c index 00b9f06f63994..6779d071a5f99 100644 --- a/ext/standard/uniqid.c +++ b/ext/standard/uniqid.c @@ -61,7 +61,7 @@ PHP_FUNCTION(uniqid) * a pause of around 10ms. */ (void)gettimeofday((struct timeval *) &tv, (struct timezone *) NULL); - if (tv.tv_sec <= prev_tv.tv_sec || + if (tv.tv_sec < prev_tv.tv_sec || (tv.tv_sec == prev_tv.tv_sec && tv.tv_usec <= prev_tv.tv_usec)) { tv.tv_sec = prev_tv.tv_sec; tv.tv_usec = prev_tv.tv_usec + 1;