From 56fe4c3492cce57c220b3c2482ddd98c907a53c6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 6 Mar 2017 11:29:25 +0200 Subject: [PATCH 1/2] bpo-24821: Fixed the slowing down to 25 times in the searching of some unlucky Unicode characters. --- Doc/whatsnew/3.7.rst | 5 ++++ Misc/NEWS | 3 ++ Objects/stringlib/fastsearch.h | 52 +++++++++++++++++++++++++++------- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 5c5ca147e3adf5..c6da6d20aa8b72 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -127,6 +127,11 @@ Optimizations in method calls being faster up to 20%. (Contributed by Yury Selivanov and INADA Naoki in :issue:`26110`.) +* Searching some unlucky Unicode characters (like Ukrainian capital "Є") + in a string was to 25 times slower than searching other characters. + Now it is slower only by 3 times in worst case. + (Contributed by Serhiy Storchaka in :issue:`24821`.) + Build and C API Changes ======================= diff --git a/Misc/NEWS b/Misc/NEWS index d542cf1032e5c1..9c322de922bcbd 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,9 @@ What's New in Python 3.7.0 alpha 1? Core and Builtins ----------------- +- bpo-24821: Fixed the slowing down to 25 times in the searching of some + unlucky Unicode characters. + - bpo-29695: Using "x" as a keyword argument in int(), bool() and float() and using "sequence" as a keyword argument in list() and tuple() are deprecated. Specify the value as a positional argument instead. diff --git a/Objects/stringlib/fastsearch.h b/Objects/stringlib/fastsearch.h index 98165ad114623a..ccfae5cded27df 100644 --- a/Objects/stringlib/fastsearch.h +++ b/Objects/stringlib/fastsearch.h @@ -32,6 +32,12 @@ #define STRINGLIB_BLOOM(mask, ch) \ ((mask & (1UL << ((ch) & (STRINGLIB_BLOOM_WIDTH -1))))) +#if STRINGLIB_SIZEOF_CHAR == 1 +# define MEMCHR_CUT_OFF 15 +#else +# define MEMCHR_CUT_OFF 40 +#endif + Py_LOCAL_INLINE(Py_ssize_t) STRINGLIB(find_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch) { @@ -39,7 +45,7 @@ STRINGLIB(find_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch) p = s; e = s + n; - if (n > 10) { + if (n > MEMCHR_CUT_OFF) { #if STRINGLIB_SIZEOF_CHAR == 1 p = memchr(s, ch, n); if (p != NULL) @@ -48,25 +54,35 @@ STRINGLIB(find_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch) #else /* use memchr if we can choose a needle without two many likely false positives */ + const STRINGLIB_CHAR *s1, *e1; unsigned char needle = ch & 0xff; /* If looking for a multiple of 256, we'd have too many false positives looking for the '\0' byte in UCS2 and UCS4 representations. */ - if (needle != 0) { - while (p < e) { + if (needle != 0) + while (e - p > MEMCHR_CUT_OFF) { void *candidate = memchr(p, needle, (e - p) * sizeof(STRINGLIB_CHAR)); if (candidate == NULL) return -1; + s1 = p; p = (const STRINGLIB_CHAR *) _Py_ALIGN_DOWN(candidate, sizeof(STRINGLIB_CHAR)); if (*p == ch) return (p - s); /* False positive */ p++; + if (p - s1 > MEMCHR_CUT_OFF) + continue; + if (e - p <= MEMCHR_CUT_OFF) + break; + e1 = p + MEMCHR_CUT_OFF; + while (p != e1) { + if (*p == ch) + return (p - s); + p++; + } } - return -1; - } #endif } while (p < e) { @@ -86,34 +102,46 @@ STRINGLIB(rfind_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch) it doesn't seem as optimized as memchr(), but is still quite faster than our hand-written loop below */ - if (n > 10) { + if (n > MEMCHR_CUT_OFF) { #if STRINGLIB_SIZEOF_CHAR == 1 - p = memrchr(s, ch, n); + p = memrchr(s, ch, n * sizeof(STRINGLIB_CHAR)); if (p != NULL) return (p - s); return -1; #else /* use memrchr if we can choose a needle without two many likely false positives */ + const STRINGLIB_CHAR *s1; + Py_ssize_t n1; unsigned char needle = ch & 0xff; /* If looking for a multiple of 256, we'd have too many false positives looking for the '\0' byte in UCS2 and UCS4 representations. */ - if (needle != 0) { - while (n > 0) { + if (needle != 0) + while (n > MEMCHR_CUT_OFF) { void *candidate = memrchr(s, needle, n * sizeof(STRINGLIB_CHAR)); if (candidate == NULL) return -1; + n1 = n; p = (const STRINGLIB_CHAR *) _Py_ALIGN_DOWN(candidate, sizeof(STRINGLIB_CHAR)); n = p - s; if (*p == ch) return n; /* False positive */ + if (n1 - n > MEMCHR_CUT_OFF) + continue; + if (n <= MEMCHR_CUT_OFF) + break; + s1 = p - MEMCHR_CUT_OFF; + while (p > s1) { + p--; + if (*p == ch) + return (p - s); + } + n = p - s; } - return -1; - } #endif } #endif /* HAVE_MEMRCHR */ @@ -126,6 +154,8 @@ STRINGLIB(rfind_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch) return -1; } +#undef MEMCHR_CUT_OFF + Py_LOCAL_INLINE(Py_ssize_t) FASTSEARCH(const STRINGLIB_CHAR* s, Py_ssize_t n, const STRINGLIB_CHAR* p, Py_ssize_t m, From 777fe7ad8a80a1218e8aabc85f17a16eb2500ad9 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 29 Mar 2017 10:38:43 +0300 Subject: [PATCH 2/2] Address Xiang's comments. --- Objects/stringlib/fastsearch.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Objects/stringlib/fastsearch.h b/Objects/stringlib/fastsearch.h index ccfae5cded27df..a8a51d577f3f7c 100644 --- a/Objects/stringlib/fastsearch.h +++ b/Objects/stringlib/fastsearch.h @@ -59,8 +59,8 @@ STRINGLIB(find_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch) /* If looking for a multiple of 256, we'd have too many false positives looking for the '\0' byte in UCS2 and UCS4 representations. */ - if (needle != 0) - while (e - p > MEMCHR_CUT_OFF) { + if (needle != 0) { + do { void *candidate = memchr(p, needle, (e - p) * sizeof(STRINGLIB_CHAR)); if (candidate == NULL) @@ -83,6 +83,8 @@ STRINGLIB(find_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch) p++; } } + while (e - p > MEMCHR_CUT_OFF); + } #endif } while (p < e) { @@ -104,7 +106,7 @@ STRINGLIB(rfind_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch) if (n > MEMCHR_CUT_OFF) { #if STRINGLIB_SIZEOF_CHAR == 1 - p = memrchr(s, ch, n * sizeof(STRINGLIB_CHAR)); + p = memrchr(s, ch, n); if (p != NULL) return (p - s); return -1; @@ -117,8 +119,8 @@ STRINGLIB(rfind_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch) /* If looking for a multiple of 256, we'd have too many false positives looking for the '\0' byte in UCS2 and UCS4 representations. */ - if (needle != 0) - while (n > MEMCHR_CUT_OFF) { + if (needle != 0) { + do { void *candidate = memrchr(s, needle, n * sizeof(STRINGLIB_CHAR)); if (candidate == NULL) @@ -142,6 +144,8 @@ STRINGLIB(rfind_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch) } n = p - s; } + while (n > MEMCHR_CUT_OFF); + } #endif } #endif /* HAVE_MEMRCHR */