Commit dc7962c43e4 for php
commit dc7962c43e417579c276f4fedfab4c3d53f45799
Author: Marc <m@pyc.ac>
Date: Thu Sep 24 17:26:05 2026 +0200
perf: use nl_langinfo for the decimal point on glibc ZTS instead of mutex-protected localeconv (#22728)
diff --git a/ext/standard/formatted_print.c b/ext/standard/formatted_print.c
index f5ac2ce5845..aa6723f8698 100644
--- a/ext/standard/formatted_print.c
+++ b/ext/standard/formatted_print.c
@@ -18,8 +18,8 @@
#include <locale.h>
#ifdef ZTS
-#include "ext/standard/php_string.h" /* for localeconv_r() */
-#define LCONV_DECIMAL_POINT (*lconv.decimal_point)
+#include "ext/standard/php_string.h" /* for localeconv_decimal_point() */
+#define LCONV_DECIMAL_POINT localeconv_decimal_point()
#else
#define LCONV_DECIMAL_POINT (*lconv->decimal_point)
#endif
@@ -221,9 +221,7 @@ php_sprintf_appenddouble(zend_string **buffer, size_t *pos,
char *s = NULL;
size_t s_len = 0;
bool is_negative = false;
-#ifdef ZTS
- struct lconv lconv;
-#else
+#ifndef ZTS
struct lconv *lconv;
#endif
@@ -256,9 +254,7 @@ php_sprintf_appenddouble(zend_string **buffer, size_t *pos,
case 'E':
case 'f':
case 'F':
-#ifdef ZTS
- localeconv_r(&lconv);
-#else
+#ifndef ZTS
lconv = localeconv();
#endif
s = php_conv_fp((fmt == 'f')?'F':fmt, number, 0, precision,
@@ -285,9 +281,7 @@ php_sprintf_appenddouble(zend_string **buffer, size_t *pos,
char decimal_point = '.';
if (fmt == 'g' || fmt == 'G') {
-#ifdef ZTS
- localeconv_r(&lconv);
-#else
+#ifndef ZTS
lconv = localeconv();
#endif
decimal_point = LCONV_DECIMAL_POINT;
diff --git a/ext/standard/php_string.h b/ext/standard/php_string.h
index 86c331f8a7c..412a4a4fa7a 100644
--- a/ext/standard/php_string.h
+++ b/ext/standard/php_string.h
@@ -32,6 +32,9 @@ PHP_MINIT_FUNCTION(string_intrin);
strnatcmp_ex(a, strlen(a), b, strlen(b), true)
PHPAPI int strnatcmp_ex(char const *a, size_t a_len, char const *b, size_t b_len, bool is_case_insensitive);
PHPAPI struct lconv *localeconv_r(struct lconv *out);
+#ifdef ZTS
+PHPAPI char localeconv_decimal_point(void);
+#endif
PHPAPI char *php_strtr(char *str, size_t len, const char *str_from, const char *str_to, size_t trlen);
PHPAPI zend_string *php_addslashes(zend_string *str);
PHPAPI void php_stripslashes(zend_string *str);
diff --git a/ext/standard/string.c b/ext/standard/string.c
index 441890ea290..bfe0c71795b 100644
--- a/ext/standard/string.c
+++ b/ext/standard/string.c
@@ -19,6 +19,9 @@
#include "php_string.h"
#include "php_variables.h"
#include <locale.h>
+#ifdef HAVE_NL_LANGINFO
+# include <langinfo.h>
+#endif
#ifdef HAVE_LANGINFO_H
# include <langinfo.h>
#endif
@@ -88,6 +91,20 @@ static zend_string *php_hex2bin(const unsigned char *old, const size_t oldlen)
}
/* }}} */
+#ifdef ZTS
+/* read the decimal point through nl_langinfo() (thread-safe), instead of taking the lock. */
+PHPAPI char localeconv_decimal_point(void)
+{
+#if defined(HAVE_NL_LANGINFO) && (defined(__GLIBC__) || defined(__MUSL__))
+ return *nl_langinfo(RADIXCHAR);
+#else
+ struct lconv lc;
+ localeconv_r(&lc);
+ return *lc.decimal_point;
+#endif
+}
+#endif
+
/* {{{ localeconv_r
* glibc's localeconv is not reentrant, so lets make it so ... sorta */
PHPAPI struct lconv *localeconv_r(struct lconv *out)
diff --git a/main/snprintf.c b/main/snprintf.c
index 73c981a1cee..9e9655fb70a 100644
--- a/main/snprintf.c
+++ b/main/snprintf.c
@@ -30,7 +30,7 @@
#include <locale.h>
#ifdef ZTS
#include "ext/standard/php_string.h"
-#define LCONV_DECIMAL_POINT (*lconv.decimal_point)
+#define LCONV_DECIMAL_POINT localeconv_decimal_point()
#else
#define LCONV_DECIMAL_POINT (*lconv->decimal_point)
#endif
@@ -491,9 +491,7 @@ static size_t format_converter(buffy * odp, const char *fmt, va_list ap) /* {{{
char num_buf[NUM_BUF_SIZE];
char char_buf[2]; /* for printing %% and %<unknown> */
-#ifdef ZTS
- struct lconv lconv;
-#else
+#ifndef ZTS
struct lconv *lconv = NULL;
#endif
@@ -843,9 +841,7 @@ static size_t format_converter(buffy * odp, const char *fmt, va_list ap) /* {{{
s = "INF";
s_len = 3;
} else {
-#ifdef ZTS
- localeconv_r(&lconv);
-#else
+#ifndef ZTS
if (!lconv) {
lconv = localeconv();
}
@@ -902,9 +898,7 @@ static size_t format_converter(buffy * odp, const char *fmt, va_list ap) /* {{{
/*
* * We use &num_buf[ 1 ], so that we have room for the sign
*/
-#ifdef ZTS
- localeconv_r(&lconv);
-#else
+#ifndef ZTS
if (!lconv) {
lconv = localeconv();
}
diff --git a/main/spprintf.c b/main/spprintf.c
index 8d6b80258a7..1975a002be7 100644
--- a/main/spprintf.c
+++ b/main/spprintf.c
@@ -88,7 +88,7 @@
#include <locale.h>
#ifdef ZTS
#include "ext/standard/php_string.h"
-#define LCONV_DECIMAL_POINT (*lconv.decimal_point)
+#define LCONV_DECIMAL_POINT localeconv_decimal_point()
#else
#define LCONV_DECIMAL_POINT (*lconv->decimal_point)
#endif
@@ -196,9 +196,7 @@ static void xbuf_format_converter(void *xbuf, bool is_char, const char *fmt, va_
char num_buf[NUM_BUF_SIZE];
char char_buf[2]; /* for printing %% and %<unknown> */
-#ifdef ZTS
- struct lconv lconv;
-#else
+#ifndef ZTS
struct lconv *lconv = NULL;
#endif
@@ -557,9 +555,7 @@ format_zend_string:;
s = "inf";
s_len = 3;
} else {
-#ifdef ZTS
- localeconv_r(&lconv);
-#else
+#ifndef ZTS
if (!lconv) {
lconv = localeconv();
}
@@ -615,9 +611,7 @@ format_zend_string:;
/*
* * We use &num_buf[ 1 ], so that we have room for the sign
*/
-#ifdef ZTS
- localeconv_r(&lconv);
-#else
+#ifndef ZTS
if (!lconv) {
lconv = localeconv();
}