Skip to content

Commit 0a58f72

Browse files
bpo-24821: Fixed the slowing down to 25 times in the searching of some (#505)
unlucky Unicode characters.
1 parent ba85d69 commit 0a58f72

File tree

3 files changed

+48
-6
lines changed

3 files changed

+48
-6
lines changed

Doc/whatsnew/3.7.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ Optimizations
138138
in method calls being faster up to 20%. (Contributed by Yury Selivanov and
139139
INADA Naoki in :issue:`26110`.)
140140

141+
* Searching some unlucky Unicode characters (like Ukrainian capital "Є")
142+
in a string was to 25 times slower than searching other characters.
143+
Now it is slower only by 3 times in worst case.
144+
(Contributed by Serhiy Storchaka in :issue:`24821`.)
145+
141146
* Fast implementation from standard C library is now used for functions
142147
:func:`~math.tgamma`, :func:`~math.lgamma`, :func:`~math.erf` and
143148
:func:`~math.erfc` in the :mod:`math` module. (Contributed by Serhiy

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Python 3.7.0 alpha 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- bpo-24821: Fixed the slowing down to 25 times in the searching of some
14+
unlucky Unicode characters.
15+
1316
- bpo-29894: The deprecation warning is emitted if __complex__ returns an
1417
instance of a strict subclass of complex. In a future versions of Python
1518
this can be an error.

Objects/stringlib/fastsearch.h

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,20 @@
3232
#define STRINGLIB_BLOOM(mask, ch) \
3333
((mask & (1UL << ((ch) & (STRINGLIB_BLOOM_WIDTH -1)))))
3434

35+
#if STRINGLIB_SIZEOF_CHAR == 1
36+
# define MEMCHR_CUT_OFF 15
37+
#else
38+
# define MEMCHR_CUT_OFF 40
39+
#endif
40+
3541
Py_LOCAL_INLINE(Py_ssize_t)
3642
STRINGLIB(find_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch)
3743
{
3844
const STRINGLIB_CHAR *p, *e;
3945

4046
p = s;
4147
e = s + n;
42-
if (n > 10) {
48+
if (n > MEMCHR_CUT_OFF) {
4349
#if STRINGLIB_SIZEOF_CHAR == 1
4450
p = memchr(s, ch, n);
4551
if (p != NULL)
@@ -48,24 +54,36 @@ STRINGLIB(find_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch)
4854
#else
4955
/* use memchr if we can choose a needle without two many likely
5056
false positives */
57+
const STRINGLIB_CHAR *s1, *e1;
5158
unsigned char needle = ch & 0xff;
5259
/* If looking for a multiple of 256, we'd have too
5360
many false positives looking for the '\0' byte in UCS2
5461
and UCS4 representations. */
5562
if (needle != 0) {
56-
while (p < e) {
63+
do {
5764
void *candidate = memchr(p, needle,
5865
(e - p) * sizeof(STRINGLIB_CHAR));
5966
if (candidate == NULL)
6067
return -1;
68+
s1 = p;
6169
p = (const STRINGLIB_CHAR *)
6270
_Py_ALIGN_DOWN(candidate, sizeof(STRINGLIB_CHAR));
6371
if (*p == ch)
6472
return (p - s);
6573
/* False positive */
6674
p++;
75+
if (p - s1 > MEMCHR_CUT_OFF)
76+
continue;
77+
if (e - p <= MEMCHR_CUT_OFF)
78+
break;
79+
e1 = p + MEMCHR_CUT_OFF;
80+
while (p != e1) {
81+
if (*p == ch)
82+
return (p - s);
83+
p++;
84+
}
6785
}
68-
return -1;
86+
while (e - p > MEMCHR_CUT_OFF);
6987
}
7088
#endif
7189
}
@@ -86,7 +104,7 @@ STRINGLIB(rfind_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch)
86104
it doesn't seem as optimized as memchr(), but is still quite
87105
faster than our hand-written loop below */
88106

89-
if (n > 10) {
107+
if (n > MEMCHR_CUT_OFF) {
90108
#if STRINGLIB_SIZEOF_CHAR == 1
91109
p = memrchr(s, ch, n);
92110
if (p != NULL)
@@ -95,24 +113,38 @@ STRINGLIB(rfind_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch)
95113
#else
96114
/* use memrchr if we can choose a needle without two many likely
97115
false positives */
116+
const STRINGLIB_CHAR *s1;
117+
Py_ssize_t n1;
98118
unsigned char needle = ch & 0xff;
99119
/* If looking for a multiple of 256, we'd have too
100120
many false positives looking for the '\0' byte in UCS2
101121
and UCS4 representations. */
102122
if (needle != 0) {
103-
while (n > 0) {
123+
do {
104124
void *candidate = memrchr(s, needle,
105125
n * sizeof(STRINGLIB_CHAR));
106126
if (candidate == NULL)
107127
return -1;
128+
n1 = n;
108129
p = (const STRINGLIB_CHAR *)
109130
_Py_ALIGN_DOWN(candidate, sizeof(STRINGLIB_CHAR));
110131
n = p - s;
111132
if (*p == ch)
112133
return n;
113134
/* False positive */
135+
if (n1 - n > MEMCHR_CUT_OFF)
136+
continue;
137+
if (n <= MEMCHR_CUT_OFF)
138+
break;
139+
s1 = p - MEMCHR_CUT_OFF;
140+
while (p > s1) {
141+
p--;
142+
if (*p == ch)
143+
return (p - s);
144+
}
145+
n = p - s;
114146
}
115-
return -1;
147+
while (n > MEMCHR_CUT_OFF);
116148
}
117149
#endif
118150
}
@@ -126,6 +158,8 @@ STRINGLIB(rfind_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch)
126158
return -1;
127159
}
128160

161+
#undef MEMCHR_CUT_OFF
162+
129163
Py_LOCAL_INLINE(Py_ssize_t)
130164
FASTSEARCH(const STRINGLIB_CHAR* s, Py_ssize_t n,
131165
const STRINGLIB_CHAR* p, Py_ssize_t m,

0 commit comments

Comments
 (0)