Skip to content

Commit 423e68c

Browse files
Avoid the overhead of the conversion in UTF-8 and ASCII locales
Add _Py_LocaleNeedsWcharConversion(), which memoizes the check of the locale encoding, and test it before scanning the string. Also keep using wcscoll() and wcsxfrm() when no conversion is needed, so that locale.strxfrm() returns the same keys as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 94ee8e2 commit 423e68c

4 files changed

Lines changed: 60 additions & 9 deletions

File tree

Include/internal/pycore_fileutils.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ extern void _Py_ResetForceASCII(void);
243243
// Convert between Unicode code points and the native wchar_t form for
244244
// C library functions which use the latter (such as the curses library).
245245
// Export for '_curses'.
246+
PyAPI_FUNC(int) _Py_LocaleNeedsWcharConversion(void);
246247
PyAPI_FUNC(int) _Py_UnicodeToLocaleWchar_InPlace(wchar_t *str, Py_ssize_t size);
247248
PyAPI_FUNC(void) _Py_LocaleWcharToUnicode_InPlace(wchar_t *str, Py_ssize_t size);
248249
#endif

Include/internal/pycore_runtime_structs.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ struct pyhash_runtime_state {
5252

5353
struct _fileutils_state {
5454
int force_ascii;
55+
/* Memoized result of _Py_LocaleNeedsWcharConversion() with the locale
56+
encoding it was computed for (see Python/fileutils.c). */
57+
struct _Py_wchar_conversion_state {
58+
char codeset[32];
59+
int needed;
60+
} wchar;
5561
};
5662

5763
#include "pycore_interpframe_structs.h" // _PyInterpreterFrame

Modules/_localemodule.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ _locale_strcoll_impl(PyObject *module, PyObject *os1, PyObject *os2)
407407
/*[clinic end generated code: output=82ddc6d62c76d618 input=693cd02bcbf38dd8]*/
408408
{
409409
#ifdef _Py_NON_UNICODE_WCHAR_T
410+
if (_Py_LocaleNeedsWcharConversion()) {
410411
/* wchar_t is not Unicode in non-UTF-8 locales and the collation
411412
tables are keyed by the raw locale values, so wcscoll() cannot
412413
be used with Unicode wchar_t. Encode to the locale encoding
@@ -424,7 +425,9 @@ _locale_strcoll_impl(PyObject *module, PyObject *os1, PyObject *os2)
424425
}
425426
Py_DECREF(b1);
426427
return result;
427-
#else
428+
}
429+
#endif
430+
{
428431
PyObject *result = NULL;
429432
wchar_t *ws1 = NULL, *ws2 = NULL;
430433

@@ -442,7 +445,7 @@ _locale_strcoll_impl(PyObject *module, PyObject *os1, PyObject *os2)
442445
if (ws1) PyMem_Free(ws1);
443446
if (ws2) PyMem_Free(ws2);
444447
return result;
445-
#endif
448+
}
446449
}
447450
#endif
448451

@@ -462,6 +465,7 @@ _locale_strxfrm_impl(PyObject *module, PyObject *str)
462465
/*[clinic end generated code: output=3081866ebffc01af input=1378bbe6a88b4780]*/
463466
{
464467
#ifdef _Py_NON_UNICODE_WCHAR_T
468+
if (_Py_LocaleNeedsWcharConversion()) {
465469
/* wchar_t is not Unicode in non-UTF-8 locales and the collation
466470
tables are keyed by the raw locale values, so wcsxfrm() cannot
467471
be used with Unicode wchar_t. Encode to the locale encoding
@@ -498,7 +502,9 @@ _locale_strxfrm_impl(PyObject *module, PyObject *str)
498502
error:
499503
Py_DECREF(bytes);
500504
return result;
501-
#else
505+
}
506+
#endif
507+
{
502508
Py_ssize_t n1;
503509
wchar_t *s = NULL, *buf = NULL;
504510
wchar_t dummy[1];
@@ -586,7 +592,7 @@ _locale_strxfrm_impl(PyObject *module, PyObject *str)
586592
PyMem_Free(buf);
587593
PyMem_Free(s);
588594
return result;
589-
#endif /* _Py_NON_UNICODE_WCHAR_T */
595+
}
590596
}
591597
#endif
592598

Python/fileutils.c

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -470,15 +470,37 @@ decode_ascii(const char *arg, wchar_t **wstr, size_t *wlen,
470470
/* Fall back to mbstowcs()/wcstombs(). */
471471
# define _Py_ICONV_FALLBACK (-9)
472472

473+
/* Return non-zero if wchar_t is not Unicode in the current locale, i.e. if
474+
the locale encoding is neither UTF-8 nor ASCII. The result is memoized:
475+
this is called before every conversion, and in the common case (a UTF-8
476+
locale) it is the only overhead. */
477+
int
478+
_Py_LocaleNeedsWcharConversion(void)
479+
{
480+
const char *codeset = nl_langinfo(CODESET);
481+
if (codeset == NULL) {
482+
return 0;
483+
}
484+
struct _Py_wchar_conversion_state *state = &_PyRuntime.fileutils.wchar;
485+
if (strcmp(codeset, state->codeset) == 0) {
486+
return state->needed;
487+
}
488+
int needed = (*codeset != '\0'
489+
&& strcmp(codeset, "UTF-8") != 0
490+
&& strcmp(codeset, "US-ASCII") != 0
491+
&& strcmp(codeset, "646") != 0);
492+
if (strlen(codeset) < sizeof(state->codeset)) {
493+
memcpy(state->codeset, codeset, strlen(codeset) + 1);
494+
state->needed = needed;
495+
}
496+
return needed;
497+
}
498+
473499
static iconv_t
474500
locale_iconv_open(int decode)
475501
{
476502
const char *codeset = nl_langinfo(CODESET);
477-
if (!codeset || !*codeset
478-
|| strcmp(codeset, "UTF-8") == 0
479-
|| strcmp(codeset, "US-ASCII") == 0
480-
|| strcmp(codeset, "646") == 0)
481-
{
503+
if (!_Py_LocaleNeedsWcharConversion()) {
482504
/* mbstowcs() and wcstombs() are correct for these encodings */
483505
return (iconv_t)-1;
484506
}
@@ -490,6 +512,10 @@ static int
490512
decode_locale_iconv(const char *arg, wchar_t **wstr, size_t *wlen,
491513
const char **reason, _Py_error_handler errors)
492514
{
515+
if (!_Py_LocaleNeedsWcharConversion()) {
516+
return _Py_ICONV_FALLBACK;
517+
}
518+
493519
int surrogateescape;
494520
if (get_surrogateescape(errors, &surrogateescape) < 0) {
495521
return -3;
@@ -569,6 +595,10 @@ encode_locale_iconv(const wchar_t *text, char **str,
569595
size_t *error_pos, const char **reason,
570596
int raw_malloc, _Py_error_handler errors)
571597
{
598+
if (!_Py_LocaleNeedsWcharConversion()) {
599+
return _Py_ICONV_FALLBACK;
600+
}
601+
572602
int surrogateescape;
573603
if (get_surrogateescape(errors, &surrogateescape) < 0) {
574604
return -3;
@@ -687,6 +717,10 @@ encode_locale_iconv(const wchar_t *text, char **str,
687717
int
688718
_Py_UnicodeToLocaleWchar_InPlace(wchar_t *str, Py_ssize_t size)
689719
{
720+
if (!_Py_LocaleNeedsWcharConversion()) {
721+
return 0;
722+
}
723+
690724
iconv_t cd = (iconv_t)-1;
691725
int res = 0;
692726
for (Py_ssize_t i = 0; i < size; i++) {
@@ -763,6 +797,10 @@ _Py_UnicodeToLocaleWchar_InPlace(wchar_t *str, Py_ssize_t size)
763797
void
764798
_Py_LocaleWcharToUnicode_InPlace(wchar_t *str, Py_ssize_t size)
765799
{
800+
if (!_Py_LocaleNeedsWcharConversion()) {
801+
return;
802+
}
803+
766804
iconv_t cd = (iconv_t)-1;
767805
for (Py_ssize_t i = 0; i < size; i++) {
768806
wchar_t wc = str[i];

0 commit comments

Comments
 (0)