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..ca9791ef1d5693a 100644 --- a/Objects/stringlib/find_max_char.h +++ b/Objects/stringlib/find_max_char.h @@ -5,123 +5,143 @@ # 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 words are loaded with memcpy, + so the data does not need to be aligned, and the tail re-reads the last + word of the buffer, which may overlap data already seen. 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; - } - p = _p; - if (p == _end) - break; + while (p + SIZEOF_SIZE_T * 16 <= _end) { + /* 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) { + size_t w0, w1, w2, w3; + memcpy(&w0, p + i, SIZEOF_SIZE_T); + memcpy(&w1, p + i + SIZEOF_SIZE_T, SIZEOF_SIZE_T); + memcpy(&w2, p + i + 2*SIZEOF_SIZE_T, SIZEOF_SIZE_T); + memcpy(&w3, p + i + 3*SIZEOF_SIZE_T, SIZEOF_SIZE_T); + v0 |= w0; + v1 |= w1; + v2 |= w2; + v3 |= w3; + } + value |= v0 | v1 | v2 | v3; + if (value & MASK_MAX_CHAR) { + return MAX_CHAR; } - if (*p++ & 0x80) - return 255; + p += SIZEOF_SIZE_T * 16; } - 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 + while (p + SIZEOF_SIZE_T <= _end) { + size_t a; + memcpy(&a, p, SIZEOF_SIZE_T); + value |= a; + p += SIZEOF_SIZE_T; + } -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) + if (p < _end) { + size_t n = (size_t)(_end - (const unsigned char *)begin); + if (n >= SIZEOF_SIZE_T) { + size_t a; + memcpy(&a, _end - SIZEOF_SIZE_T, SIZEOF_SIZE_T); + value |= a; + } +#if SIZEOF_SIZE_T == 8 + else if (n >= 4) { + uint32_t a, b; + memcpy(&a, p, 4); + memcpy(&b, _end - 4, 4); + value |= a | b; + } #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 (mask == MASK_ASCII) { - max_char = MAX_CHAR_UCS1; - mask = MASK_UCS1; - } - else { - /* mask can't be MASK_UCS2 because of mask_limit above */ - assert(mask == MASK_UCS1); - max_char = MAX_CHAR_UCS2; - mask = MASK_UCS2; - } - /* We check the new mask on the same chars in the next iteration */ - continue; +#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; } - 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; - } - else { - /* mask can't be MASK_UCS2 because of mask_limit above */ - assert(mask == MASK_UCS1); - max_char = MAX_CHAR_UCS2; - mask = MASK_UCS2; - } - /* We check the new mask on the same chars in the next iteration */ - continue; +#endif +#if STRINGLIB_SIZEOF_CHAR == 1 + else { + value |= *p; } - 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; } - return max_char; +#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 +149,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 */ -