Skip to content

Commit 23c4443

Browse files
committed
Optimize stripos
Closes GH-7847 Previously stripos would lowercase both the haystack and needle to reuse strpos. The approach in this PR is similar to strpos. memchr is highly optimized so we're using it to search for the first character of the needle in the haystack. If we find it we compare the remaining characters of the needle manually. The new implementation seems to perform about half as well as strpos (as two memchr calls are necessary).
1 parent a0ba104 commit 23c4443

File tree

3 files changed

+68
-9
lines changed

3 files changed

+68
-9
lines changed

Zend/zend_operators.h

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,70 @@ static zend_always_inline void zend_unwrap_reference(zval *op) /* {{{ */
919919
}
920920
/* }}} */
921921

922+
static zend_always_inline const char *strnichr(const char *ptr, char value, size_t num)
923+
{
924+
char value_lower = zend_tolower_ascii(value);
925+
char value_upper = zend_toupper_ascii(value);
926+
927+
if (value_lower == value_upper) {
928+
return (const char *)memchr(ptr, value, num);
929+
}
930+
931+
const char *ptr_lower = (const char *)memchr(ptr, value_lower, num);
932+
const char *ptr_upper = (const char *)memchr(ptr, value_upper, num);
933+
if (ptr_lower && ptr_upper) {
934+
return ptr_lower < ptr_upper ? ptr_lower : ptr_upper;
935+
}
936+
return ptr_lower ? ptr_lower : ptr_upper;
937+
}
938+
939+
static zend_always_inline bool strnieq(const char *ptr1, const char *ptr2, size_t num)
940+
{
941+
for (size_t i = 0; i < num; i++) {
942+
if (zend_tolower_ascii(*ptr1++) != zend_tolower_ascii(*ptr2++)) {
943+
return 0;
944+
}
945+
}
946+
return 1;
947+
}
948+
949+
static zend_always_inline const char *
950+
zend_memnistr(const char *haystack, const char *needle, size_t needle_len, const char *end)
951+
{
952+
const char *p = haystack;
953+
size_t off_s;
954+
955+
ZEND_ASSERT(end >= p);
956+
957+
if (needle_len == 1) {
958+
return (const char *)strnichr(p, *needle, (end-p));
959+
} else if (UNEXPECTED(needle_len == 0)) {
960+
return p;
961+
}
962+
963+
off_s = (size_t)(end - p);
964+
965+
if (needle_len > off_s) {
966+
return NULL;
967+
}
968+
969+
const char ne = zend_tolower_ascii(needle[needle_len-1]);
970+
end -= needle_len;
971+
972+
while (p <= end) {
973+
if ((p = (const char *)strnichr(p, *needle, (end-p+1)))) {
974+
if (ne == zend_tolower_ascii(p[needle_len-1]) && strnieq(needle+1, p+1, needle_len-2)) {
975+
return p;
976+
}
977+
} else {
978+
return NULL;
979+
}
980+
p++;
981+
}
982+
983+
return NULL;
984+
}
985+
922986

923987
END_EXTERN_C()
924988

ext/standard/string.c

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1890,7 +1890,6 @@ PHP_FUNCTION(stripos)
18901890
const char *found = NULL;
18911891
zend_string *haystack, *needle;
18921892
zend_long offset = 0;
1893-
zend_string *needle_dup = NULL, *haystack_dup;
18941893

18951894
ZEND_PARSE_PARAMETERS_START(2, 3)
18961895
Z_PARAM_STR(haystack)
@@ -1911,19 +1910,14 @@ PHP_FUNCTION(stripos)
19111910
RETURN_FALSE;
19121911
}
19131912

1914-
haystack_dup = zend_string_tolower(haystack);
1915-
needle_dup = zend_string_tolower(needle);
1916-
found = (char*)php_memnstr(ZSTR_VAL(haystack_dup) + offset,
1917-
ZSTR_VAL(needle_dup), ZSTR_LEN(needle_dup), ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack));
1913+
found = (char*)php_memnistr(ZSTR_VAL(haystack) + offset,
1914+
ZSTR_VAL(needle), ZSTR_LEN(needle), ZSTR_VAL(haystack) + ZSTR_LEN(haystack));
19181915

19191916
if (found) {
1920-
RETVAL_LONG(found - ZSTR_VAL(haystack_dup));
1917+
RETVAL_LONG(found - ZSTR_VAL(haystack));
19211918
} else {
19221919
RETVAL_FALSE;
19231920
}
1924-
1925-
zend_string_release_ex(haystack_dup, 0);
1926-
zend_string_release_ex(needle_dup, 0);
19271921
}
19281922
/* }}} */
19291923

main/php.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ END_EXTERN_C()
344344
#define phpin zendin
345345

346346
#define php_memnstr zend_memnstr
347+
#define php_memnistr zend_memnistr
347348

348349
/* functions */
349350
BEGIN_EXTERN_C()

0 commit comments

Comments
 (0)