https://github.com/python/cpython/commit/ff042f13775b0d905b20e425df18d35c2289cb43
commit: ff042f13775b0d905b20e425df18d35c2289cb43
branch: main
author: RM <[email protected]>
committer: StanFromIreland <[email protected]>
date: 2026-11-09T13:58:25Z
summary:

gh-122901: Speed up `str` creation from non-ASCII data with a branch-free 
`find_max_char()` (#122902)

Co-authored-by: Stan Ulbrych <[email protected]>
Co-authored-by: Victor Stinner <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2024-08-11-12-06-23.gh-issue-122901.B2Huom.rst
M Objects/stringlib/find_max_char.h

diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2024-08-11-12-06-23.gh-issue-122901.B2Huom.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2024-08-11-12-06-23.gh-issue-122901.B2Huom.rst
new file mode 100644
index 000000000000000..734bbc63f381ab4
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2024-08-11-12-06-23.gh-issue-122901.B2Huom.rst
@@ -0,0 +1 @@
+Speed up creating :class:`str` objects from non-ASCII character data.
diff --git a/Objects/stringlib/find_max_char.h 
b/Objects/stringlib/find_max_char.h
index 7ab3fc88b331b14..8c7da7b7fad38ee 100644
--- a/Objects/stringlib/find_max_char.h
+++ b/Objects/stringlib/find_max_char.h
@@ -5,123 +5,157 @@
 # error "find_max_char.h is specific to Unicode"
 #endif
 
-/* Mask to quickly check whether a C 'size_t' contains a
-   non-ASCII, UTF8-encoded char. */
-#if (SIZEOF_SIZE_T == 8)
-# define UCS1_ASCII_CHAR_MASK 0x8080808080808080ULL
-#elif (SIZEOF_SIZE_T == 4)
-# define UCS1_ASCII_CHAR_MASK 0x80808080U
-#else
-# error C 'size_t' size should be either 4 or 8!
-#endif
+#define MAX_CHAR_ASCII 0x7f
+#define MAX_CHAR_UCS1  0xff
+#define MAX_CHAR_UCS2  0xffff
+#define MAX_CHAR_UCS4  0x10ffff
 
+/* Masks selecting the bits that are set in a C 'size_t' holding several
+   characters when at least one of them is wider than ASCII, UCS1 or UCS2. */
 #if STRINGLIB_SIZEOF_CHAR == 1
+#  if SIZEOF_SIZE_T == 8
+#    define MASK_ASCII 0x8080808080808080ULL
+#  elif SIZEOF_SIZE_T == 4
+#    define MASK_ASCII 0x80808080U
+#  else
+#    error C 'size_t' size should be either 4 or 8!
+#  endif
+#  define MASK_MAX_CHAR MASK_ASCII
+#  define MAX_CHAR MAX_CHAR_UCS1
+#elif STRINGLIB_SIZEOF_CHAR == 2
+#  if SIZEOF_SIZE_T == 8
+#    define MASK_ASCII 0xFF80FF80FF80FF80ULL
+#    define MASK_UCS1 0xFF00FF00FF00FF00ULL
+#  elif SIZEOF_SIZE_T == 4
+#    define MASK_ASCII 0xFF80FF80U
+#    define MASK_UCS1 0xFF00FF00U
+#  else
+#    error C 'size_t' size should be either 4 or 8!
+#  endif
+#  define MASK_MAX_CHAR MASK_UCS1
+#  define MAX_CHAR MAX_CHAR_UCS2
+#elif STRINGLIB_SIZEOF_CHAR == 4
+#  if SIZEOF_SIZE_T == 8
+#    define MASK_ASCII 0xFFFFFF80FFFFFF80ULL
+#    define MASK_UCS1 0xFFFFFF00FFFFFF00ULL
+#    define MASK_UCS2 0xFFFF0000FFFF0000ULL
+#  elif SIZEOF_SIZE_T == 4
+#    define MASK_ASCII 0xFFFFFF80U
+#    define MASK_UCS1 0xFFFFFF00U
+#    define MASK_UCS2 0xFFFF0000U
+#  else
+#    error C 'size_t' size should be either 4 or 8!
+#  endif
+#  define MASK_MAX_CHAR MASK_UCS2
+#  define MAX_CHAR MAX_CHAR_UCS4
+#else
+#  error Invalid STRINGLIB_SIZEOF_CHAR (must be 1, 2 or 4)
+#endif
 
+/* OR all characters into a single size_t accumulator and test the high bits
+   at the end, instead of comparing each character. The accumulation loop has
+   no branches, so compilers vectorize it. The start is first read in pieces
+   until the pointer is aligned, so the loops can use plain word loads on
+   every platform. Every 16 words, check whether the widest kind is already
+   known and exit early. */
 Py_LOCAL_INLINE(Py_UCS4)
 STRINGLIB(find_max_char)(const STRINGLIB_CHAR *begin, const STRINGLIB_CHAR 
*end)
 {
-    const unsigned char *p = (const unsigned char *) begin;
+    const unsigned char *p = (const unsigned char *)begin;
     const unsigned char *_end = (const unsigned char *)end;
+    size_t value = 0;
 
-    while (p < _end) {
-        if (_Py_IS_ALIGNED(p, ALIGNOF_SIZE_T)) {
-            /* Help register allocation */
-            const unsigned char *_p = p;
-            while (_p + SIZEOF_SIZE_T <= _end) {
-                size_t value = *(const size_t *) _p;
-                if (value & UCS1_ASCII_CHAR_MASK)
-                    return 255;
-                _p += SIZEOF_SIZE_T;
+    if (p + SIZEOF_SIZE_T <= _end) {
+        if (!_Py_IS_ALIGNED(p, ALIGNOF_SIZE_T)) {
+#if STRINGLIB_SIZEOF_CHAR == 1
+            if (!_Py_IS_ALIGNED(p, sizeof(uint16_t))) {
+                value |= *p++;
             }
-            p = _p;
-            if (p == _end)
-                break;
-        }
-        if (*p++ & 0x80)
-            return 255;
-    }
-    return 127;
-}
-
-#undef ASCII_CHAR_MASK
-
-#else /* STRINGLIB_SIZEOF_CHAR == 1 */
-
-#define MASK_ASCII 0xFFFFFF80
-#define MASK_UCS1 0xFFFFFF00
-#define MASK_UCS2 0xFFFF0000
-
-#define MAX_CHAR_ASCII 0x7f
-#define MAX_CHAR_UCS1  0xff
-#define MAX_CHAR_UCS2  0xffff
-#define MAX_CHAR_UCS4  0x10ffff
-
-Py_LOCAL_INLINE(Py_UCS4)
-STRINGLIB(find_max_char)(const STRINGLIB_CHAR *begin, const STRINGLIB_CHAR 
*end)
-{
-#if STRINGLIB_SIZEOF_CHAR == 2
-    const Py_UCS4 mask_limit = MASK_UCS1;
-    const Py_UCS4 max_char_limit = MAX_CHAR_UCS2;
-#elif STRINGLIB_SIZEOF_CHAR == 4
-    const Py_UCS4 mask_limit = MASK_UCS2;
-    const Py_UCS4 max_char_limit = MAX_CHAR_UCS4;
-#else
-#error Invalid STRINGLIB_SIZEOF_CHAR (must be 1, 2 or 4)
 #endif
-    Py_UCS4 mask;
-    Py_ssize_t n = end - begin;
-    const STRINGLIB_CHAR *p = begin;
-    const STRINGLIB_CHAR *unrolled_end = begin + _Py_SIZE_ROUND_DOWN(n, 4);
-    Py_UCS4 max_char;
-
-    max_char = MAX_CHAR_ASCII;
-    mask = MASK_ASCII;
-    while (p < unrolled_end) {
-        STRINGLIB_CHAR bits = p[0] | p[1] | p[2] | p[3];
-        if (bits & mask) {
-            if (mask == mask_limit) {
-                /* Limit reached */
-                return max_char_limit;
+#if STRINGLIB_SIZEOF_CHAR <= 2
+            if (!_Py_IS_ALIGNED(p, sizeof(uint32_t))) {
+                value |= *(const uint16_t *)p;
+                p += sizeof(uint16_t);
             }
-            if (mask == MASK_ASCII) {
-                max_char = MAX_CHAR_UCS1;
-                mask = MASK_UCS1;
+#endif
+#if SIZEOF_SIZE_T == 8
+            if (!_Py_IS_ALIGNED(p, SIZEOF_SIZE_T)) {
+                value |= *(const uint32_t *)p;
+                p += sizeof(uint32_t);
             }
-            else {
-                /* mask can't be MASK_UCS2 because of mask_limit above */
-                assert(mask == MASK_UCS1);
-                max_char = MAX_CHAR_UCS2;
-                mask = MASK_UCS2;
+#endif
+            if (value & MASK_MAX_CHAR) {
+                return MAX_CHAR;
             }
-            /* We check the new mask on the same chars in the next iteration */
-            continue;
+            assert(_Py_IS_ALIGNED(p, ALIGNOF_SIZE_T));
         }
-        p += 4;
-    }
-    while (p < end) {
-        if (p[0] & mask) {
-            if (mask == mask_limit) {
-                /* Limit reached */
-                return max_char_limit;
-            }
-            if (mask == MASK_ASCII) {
-                max_char = MAX_CHAR_UCS1;
-                mask = MASK_UCS1;
+
+        for (; p + SIZEOF_SIZE_T * 16 <= _end; p += SIZEOF_SIZE_T * 16) {
+            /* Four independent accumulators so the loop does not serialize on
+               one register when the compiler does not vectorize it. */
+            size_t v0 = 0, v1 = 0, v2 = 0, v3 = 0;
+            for (int i = 0; i < 16 * SIZEOF_SIZE_T; i += 4 * SIZEOF_SIZE_T) {
+                const size_t *w = (const size_t *)(p + i);
+                v0 |= w[0];
+                v1 |= w[1];
+                v2 |= w[2];
+                v3 |= w[3];
             }
-            else {
-                /* mask can't be MASK_UCS2 because of mask_limit above */
-                assert(mask == MASK_UCS1);
-                max_char = MAX_CHAR_UCS2;
-                mask = MASK_UCS2;
+            size_t block = v0 | v1 | v2 | v3;
+            if (block & MASK_MAX_CHAR) {
+                return MAX_CHAR;
             }
-            /* We check the new mask on the same chars in the next iteration */
-            continue;
+            value |= block;
+        }
+
+        for (; p + SIZEOF_SIZE_T <= _end; p += SIZEOF_SIZE_T) {
+            value |= *(const size_t *)p;
         }
-        p++;
     }
-    return max_char;
+
+    /* Fewer than SIZEOF_SIZE_T bytes are left. Read them as two overlapping
+       pieces, one from each end. OR does not mind seeing a byte twice. */
+    if (p < _end) {
+        size_t n = (size_t)(_end - p);
+        if (n >= 4) {
+            uint32_t a, b;
+            memcpy(&a, p, 4);
+            memcpy(&b, _end - 4, 4);
+            value |= a | b;
+        }
+#if STRINGLIB_SIZEOF_CHAR <= 2
+        else if (n >= 2) {
+            uint16_t a, b;
+            memcpy(&a, p, 2);
+            memcpy(&b, _end - 2, 2);
+            value |= a | b;
+        }
+#endif
+#if STRINGLIB_SIZEOF_CHAR == 1
+        else {
+            value |= *p;
+        }
+#endif
+    }
+
+#ifdef MASK_UCS2
+    if (value & MASK_UCS2) {
+        return MAX_CHAR_UCS4;
+    }
+#endif
+#ifdef MASK_UCS1
+    if (value & MASK_UCS1) {
+        return MAX_CHAR_UCS2;
+    }
+#endif
+    if (value & MASK_ASCII) {
+        return MAX_CHAR_UCS1;
+    }
+    return MAX_CHAR_ASCII;
 }
 
+#undef MASK_MAX_CHAR
+#undef MAX_CHAR
 #undef MASK_ASCII
 #undef MASK_UCS1
 #undef MASK_UCS2
@@ -129,6 +163,3 @@ STRINGLIB(find_max_char)(const STRINGLIB_CHAR *begin, const 
STRINGLIB_CHAR *end)
 #undef MAX_CHAR_UCS1
 #undef MAX_CHAR_UCS2
 #undef MAX_CHAR_UCS4
-
-#endif /* STRINGLIB_SIZEOF_CHAR == 1 */
-

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to