Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2c3b230

Browse files
committed
Issue #13134: optimize finding single-character strings using memchr
1 parent 60dd7dc commit 2c3b230

4 files changed

Lines changed: 95 additions & 1 deletion

File tree

Lib/test/test_unicode.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,15 @@ def test_count(self):
171171

172172
def test_find(self):
173173
string_tests.CommonTest.test_find(self)
174+
# test implementation details of the memchr fast path
175+
self.checkequal(100, 'a' * 100 + '\u0102', 'find', '\u0102')
176+
self.checkequal(-1, 'a' * 100 + '\u0102', 'find', '\u0201')
177+
self.checkequal(-1, 'a' * 100 + '\u0102', 'find', '\u0120')
178+
self.checkequal(-1, 'a' * 100 + '\u0102', 'find', '\u0220')
179+
self.checkequal(100, 'a' * 100 + '\U00100304', 'find', '\U00100304')
180+
self.checkequal(-1, 'a' * 100 + '\U00100304', 'find', '\U00100204')
181+
self.checkequal(-1, 'a' * 100 + '\U00100304', 'find', '\U00102004')
182+
# check mixed argument types
174183
self.checkequalnofix(0, 'abcdefghiabc', 'find', 'abc')
175184
self.checkequalnofix(9, 'abcdefghiabc', 'find', 'abc', 1)
176185
self.checkequalnofix(-1, 'abcdefghiabc', 'find', 'def', 4)
@@ -180,6 +189,14 @@ def test_find(self):
180189

181190
def test_rfind(self):
182191
string_tests.CommonTest.test_rfind(self)
192+
# test implementation details of the memrchr fast path
193+
self.checkequal(0, '\u0102' + 'a' * 100 , 'rfind', '\u0102')
194+
self.checkequal(-1, '\u0102' + 'a' * 100 , 'rfind', '\u0201')
195+
self.checkequal(-1, '\u0102' + 'a' * 100 , 'rfind', '\u0120')
196+
self.checkequal(-1, '\u0102' + 'a' * 100 , 'rfind', '\u0220')
197+
self.checkequal(0, '\U00100304' + 'a' * 100, 'rfind', '\U00100304')
198+
self.checkequal(-1, '\U00100304' + 'a' * 100, 'rfind', '\U00100204')
199+
self.checkequal(-1, '\U00100304' + 'a' * 100, 'rfind', '\U00102004')
183200
# check mixed argument types
184201
self.checkequalnofix(9, 'abcdefghiabc', 'rfind', 'abc')
185202
self.checkequalnofix(12, 'abcdefghiabc', 'rfind', '')

Objects/stringlib/fastsearch.h

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

35+
36+
Py_LOCAL_INLINE(Py_ssize_t)
37+
STRINGLIB(fastsearch_memchr_1char)(const STRINGLIB_CHAR* s, Py_ssize_t n,
38+
STRINGLIB_CHAR ch, unsigned char needle,
39+
Py_ssize_t maxcount, int mode)
40+
{
41+
void *candidate;
42+
const STRINGLIB_CHAR *found;
43+
44+
#define DO_MEMCHR(memchr, s, needle, nchars) do { \
45+
candidate = memchr((const void *) (s), (needle), (nchars) * sizeof(STRINGLIB_CHAR)); \
46+
found = (const STRINGLIB_CHAR *) \
47+
((Py_ssize_t) candidate & (~ ((Py_ssize_t) sizeof(STRINGLIB_CHAR) - 1))); \
48+
} while (0)
49+
50+
if (mode == FAST_SEARCH) {
51+
const STRINGLIB_CHAR *_s = s;
52+
const STRINGLIB_CHAR *e = s + n;
53+
while (_s < e) {
54+
DO_MEMCHR(memchr, _s, needle, e - _s);
55+
if (found == NULL)
56+
return -1;
57+
if (sizeof(STRINGLIB_CHAR) == 1 || *found == ch)
58+
return (found - _s);
59+
/* False positive */
60+
_s = found + 1;
61+
}
62+
return -1;
63+
}
64+
#ifdef HAVE_MEMRCHR
65+
/* memrchr() is a GNU extension, available since glibc 2.1.91.
66+
it doesn't seem as optimized as memchr(), but is still quite
67+
faster than our hand-written loop in FASTSEARCH below */
68+
else if (mode == FAST_RSEARCH) {
69+
while (n > 0) {
70+
DO_MEMCHR(memrchr, s, needle, n);
71+
if (found == NULL)
72+
return -1;
73+
n = found - s;
74+
if (sizeof(STRINGLIB_CHAR) == 1 || *found == ch)
75+
return n;
76+
/* False positive */
77+
}
78+
return -1;
79+
}
80+
#endif
81+
else {
82+
assert(0); /* Should never get here */
83+
return 0;
84+
}
85+
86+
#undef DO_MEMCHR
87+
}
88+
3589
Py_LOCAL_INLINE(Py_ssize_t)
3690
FASTSEARCH(const STRINGLIB_CHAR* s, Py_ssize_t n,
3791
const STRINGLIB_CHAR* p, Py_ssize_t m,
@@ -51,6 +105,25 @@ FASTSEARCH(const STRINGLIB_CHAR* s, Py_ssize_t n,
51105
if (m <= 0)
52106
return -1;
53107
/* use special case for 1-character strings */
108+
if (n > 10 && (mode == FAST_SEARCH
109+
#ifdef HAVE_MEMRCHR
110+
|| mode == FAST_RSEARCH
111+
#endif
112+
)) {
113+
/* use memchr if we can choose a needle without two many likely
114+
false positives */
115+
unsigned char needle;
116+
int use_needle = 1;
117+
needle = p[0] & 0xff;
118+
if (needle == 0 && sizeof(STRINGLIB_CHAR) > 1) {
119+
needle = (p[0] >> 8) & 0xff;
120+
if (needle >= 32)
121+
use_needle = 0;
122+
}
123+
if (use_needle)
124+
return STRINGLIB(fastsearch_memchr_1char)
125+
(s, n, p[0], needle, maxcount, mode);
126+
}
54127
if (mode == FAST_COUNT) {
55128
for (i = 0; i < n; i++)
56129
if (s[i] == p[0]) {

configure.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2566,7 +2566,8 @@ AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
25662566
getgrouplist getgroups getlogin getloadavg getpeername getpgid getpid \
25672567
getpriority getresuid getresgid getpwent getspnam getspent getsid getwd \
25682568
if_nameindex \
2569-
initgroups kill killpg lchmod lchown lockf linkat lstat lutimes mbrtowc mkdirat mkfifo \
2569+
initgroups kill killpg lchmod lchown lockf linkat lstat lutimes memrchr \
2570+
mbrtowc mkdirat mkfifo \
25702571
mkfifoat mknod mknodat mktime mremap nice openat pathconf pause pipe2 plock poll \
25712572
posix_fallocate posix_fadvise pread \
25722573
pthread_init pthread_kill putenv pwrite readlink readlinkat readv realpath renameat \

pyconfig.h.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,9 @@
515515
/* Define to 1 if you have the <memory.h> header file. */
516516
#undef HAVE_MEMORY_H
517517

518+
/* Define to 1 if you have the `memrchr' function. */
519+
#undef HAVE_MEMRCHR
520+
518521
/* Define to 1 if you have the `mkdirat' function. */
519522
#undef HAVE_MKDIRAT
520523

0 commit comments

Comments
 (0)