Commit 8df533e4d31 for php
commit 8df533e4d315722599473dc4d7c6cf107107eeff
Author: Artem Ukrainskiy <art.ukrainskiy@gmail.com>
Date: Tue Nov 3 13:15:44 2026 +0300
ext/mysqlnd: Use relaxed atomics for statistics in ZTS builds (#24448)
In ZTS builds every statistics update locked a mutex, and the global
statistics are one object shared by all threads of the process. The
values are now atomic_uint_least64_t updated with relaxed atomic
additions, and LOCK_access is removed. NTS builds are unchanged.
Some 32-bit targets (ARMv5, MIPS, PowerPC, RISC-V) implement 64-bit
atomics in libatomic; there configure links mysqlnd with -latomic in
ZTS builds.
FrankenPHP 1.13.1 in worker mode with 16 threads, one prepared SELECT
of 20 rows per request, AMD Ryzen 9 5950X, GCC 15.2. CPU time per
request with statistics on (the default), by the cores FrankenPHP
runs on:
- 4 cores of one CCD: 0.3029 -> 0.2722 ms (-10%)
- 4 cores, two on each CCD: 0.3553 -> 0.2934 ms (-17%)
- 8 cores of one CCD: 0.3810 -> 0.2739 ms (-28%)
- 8 cores, four on each CCD: 0.5274 -> 0.2781 ms (-47%)
Statistics on now cost 0.6-1.4% over statistics off, against 12%,
23%, 41% and 91% before.
diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS
index 9eae8540351..d15be269f14 100644
--- a/UPGRADING.INTERNALS
+++ b/UPGRADING.INTERNALS
@@ -80,6 +80,13 @@ PHP 8.7 INTERNALS UPGRADE NOTES
mysqlnd_reverse_api_register_api() and mysqlnd_reverse_api_get_api_list()
no longer exist.
+- Mysqlnd:
+ . In ZTS builds the values of MYSQLND_STATS are atomic_uint_least64_t and
+ the LOCK_access member was removed. The MYSQLND_INC_STATISTIC*() and
+ MYSQLND_DEC_STATISTIC() macros update them with relaxed atomic additions.
+ . The MYSQLND_STATS_LOCK() and MYSQLND_STATS_UNLOCK() macros were removed.
+ MYSQLND_STATS_UPDATE_VALUE() is now an expression instead of a block.
+
- Standard:
. php_explode() now takes a HashTable* instead of a zval* to store the
resulting parts. It already expected the zval* to be IS_ARRAY.
diff --git a/ext/mysqlnd/config9.m4 b/ext/mysqlnd/config9.m4
index 8a83a16327f..81f41fd22de 100644
--- a/ext/mysqlnd/config9.m4
+++ b/ext/mysqlnd/config9.m4
@@ -69,6 +69,29 @@ if test "$PHP_MYSQLND" != "no" || test "$PHP_MYSQLND_ENABLED" = "yes"; then
PHP_ADD_EXTENSION_DEP(mysqlnd, hash)
])])
+ dnl The statistics are 64-bit atomics in ZTS builds, which some 32-bit targets
+ dnl implement in libatomic.
+ AS_VAR_IF([PHP_THREAD_SAFETY], [yes], [
+ AC_CACHE_CHECK([for the library of 64-bit atomic operations],
+ [php_cv_lib_atomic_u64],
+ [php_cv_lib_atomic_u64=no
+ for php_lib in "" -latomic; do
+ php_save_LIBS=$LIBS
+ LIBS="$LIBS $php_lib"
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([
+ #include <stdatomic.h>
+ #include <stdint.h>
+ atomic_uint_least64_t value;
+ ], [return (int) atomic_fetch_add_explicit(&value, 1, memory_order_relaxed);])],
+ [php_cv_lib_atomic_u64=${php_lib:-none required}])
+ LIBS=$php_save_LIBS
+ AS_VAR_IF([php_cv_lib_atomic_u64], [no],, [break])
+ done])
+ AS_CASE([$php_cv_lib_atomic_u64],
+ [no], [AC_MSG_FAILURE([64-bit atomic operations are not available.])],
+ [-latomic], [PHP_ADD_LIBRARY([atomic],, [MYSQLND_SHARED_LIBADD])])
+ ])
+
PHP_INSTALL_HEADERS([ext/mysqlnd/])
PHP_SUBST([MYSQLND_SHARED_LIBADD])
fi
diff --git a/ext/mysqlnd/mysqlnd_block_alloc.c b/ext/mysqlnd/mysqlnd_block_alloc.c
index db836d3f7b9..b75bbedabec 100644
--- a/ext/mysqlnd/mysqlnd_block_alloc.c
+++ b/ext/mysqlnd/mysqlnd_block_alloc.c
@@ -53,7 +53,6 @@ PHPAPI void
mysqlnd_mempool_destroy(MYSQLND_MEMORY_POOL * pool)
{
DBG_ENTER("mysqlnd_mempool_destroy");
- /* mnd_free will reference LOCK_access and might crash, depending on the caller...*/
zend_arena_destroy(pool->arena);
DBG_VOID_RETURN;
}
diff --git a/ext/mysqlnd/mysqlnd_driver.c b/ext/mysqlnd/mysqlnd_driver.c
index 4c2d584e7ba..78a14845ff5 100644
--- a/ext/mysqlnd/mysqlnd_driver.c
+++ b/ext/mysqlnd/mysqlnd_driver.c
@@ -69,7 +69,6 @@ PHPAPI void mysqlnd_library_init(void)
mysqlnd_conn_set_methods(&MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_conn));
mysqlnd_conn_data_set_methods(&MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_conn_data));
_mysqlnd_init_ps_subsystem();
- /* Should be calloc, as mnd_calloc will reference LOCK_access*/
mysqlnd_stats_init(&mysqlnd_global_stats, STAT_LAST, 1);
mysqlnd_plugin_subsystem_init();
{
diff --git a/ext/mysqlnd/mysqlnd_statistics.c b/ext/mysqlnd/mysqlnd_statistics.c
index 33a47843c9c..ed5e068c459 100644
--- a/ext/mysqlnd/mysqlnd_statistics.c
+++ b/ext/mysqlnd/mysqlnd_statistics.c
@@ -201,7 +201,12 @@ mysqlnd_fill_stats_hash(const MYSQLND_STATS * const stats, const MYSQLND_STRING
for (i = 0; i < stats->count; i++) {
char tmp[25];
- snprintf(tmp, sizeof(tmp), "%" PRIu64, stats->values[i]);
+#ifdef ZTS
+ uint64_t value = atomic_load_explicit(&stats->values[i], memory_order_relaxed);
+#else
+ uint64_t value = stats->values[i];
+#endif
+ snprintf(tmp, sizeof(tmp), "%" PRIu64, value);
add_assoc_string_ex(return_value, names[i].s, names[i].l, tmp);
}
}
@@ -216,7 +221,9 @@ mysqlnd_stats_init(MYSQLND_STATS ** stats, const size_t statistic_count, const b
*stats = pecalloc(1, size, persistent);
(*stats)->count = statistic_count;
#ifdef ZTS
- (*stats)->LOCK_access = tsrm_mutex_alloc();
+ for (size_t i = 0; i < statistic_count; i++) {
+ atomic_init(&(*stats)->values[i], 0);
+ }
#endif
}
/* }}} */
@@ -226,10 +233,7 @@ mysqlnd_stats_init(MYSQLND_STATS ** stats, const size_t statistic_count, const b
PHPAPI void
mysqlnd_stats_end(MYSQLND_STATS * stats, const bool persistent)
{
-#ifdef ZTS
- tsrm_mutex_free(stats->LOCK_access);
-#endif
- /* mnd_free will reference LOCK_access and crash...*/
+ /* mnd_free would update the statistics being freed */
pefree(stats, persistent);
}
/* }}} */
diff --git a/ext/mysqlnd/mysqlnd_statistics.h b/ext/mysqlnd/mysqlnd_statistics.h
index a576f67ffdb..f4f2406b85e 100644
--- a/ext/mysqlnd/mysqlnd_statistics.h
+++ b/ext/mysqlnd/mysqlnd_statistics.h
@@ -17,19 +17,12 @@
#define MYSQLND_STATISTICS_H
#ifdef ZTS
-#define MYSQLND_STATS_LOCK(stats) tsrm_mutex_lock((stats)->LOCK_access)
-#define MYSQLND_STATS_UNLOCK(stats) tsrm_mutex_unlock((stats)->LOCK_access)
+#define MYSQLND_STATS_UPDATE_VALUE(stats, statistic, value) \
+ atomic_fetch_add_explicit(&(stats)->values[(statistic)], (value), memory_order_relaxed)
#else
-#define MYSQLND_STATS_LOCK(stats)
-#define MYSQLND_STATS_UNLOCK(stats)
-#endif
-
#define MYSQLND_STATS_UPDATE_VALUE(stats, statistic, value) \
- { \
- MYSQLND_STATS_LOCK(stats); \
- (stats)->values[(statistic)] += (value); \
- MYSQLND_STATS_UNLOCK(_p_s); \
- }
+ ((stats)->values[(statistic)] += (value))
+#endif
#define MYSQLND_DEC_STATISTIC(enabler, stats, statistic) \
{ \
diff --git a/ext/mysqlnd/mysqlnd_structs.h b/ext/mysqlnd/mysqlnd_structs.h
index 80d0abfcae6..73c0e04a236 100644
--- a/ext/mysqlnd/mysqlnd_structs.h
+++ b/ext/mysqlnd/mysqlnd_structs.h
@@ -292,13 +292,24 @@ typedef enum_func_status (*mysqlnd_fetch_row_func)(MYSQLND_RES *result,
typedef struct st_mysqlnd_stats MYSQLND_STATS;
+#if defined(ZTS) && defined(__cplusplus)
+/* The C11 atomics used by struct st_mysqlnd_stats and the MYSQLND_*_STATISTIC*() macros */
+extern "C++" {
+using std::atomic_uint_least64_t;
+using std::atomic_fetch_add_explicit;
+using std::atomic_load_explicit;
+using std::memory_order_relaxed;
+}
+#endif
+
struct st_mysqlnd_stats
{
size_t count;
#ifdef ZTS
- MUTEX_T LOCK_access;
-#endif
+ atomic_uint_least64_t values[] ZEND_ELEMENT_COUNT(count);
+#else
uint64_t values[] ZEND_ELEMENT_COUNT(count);
+#endif
};