Skip to content

Commit 085b360

Browse files
committed
Improve strtr(), str_replace() and substr_count() performance
Use SSE2 to calculate number of occurrences of a given character in a string
1 parent c149d37 commit 085b360

File tree

1 file changed

+78
-33
lines changed

1 file changed

+78
-33
lines changed

ext/standard/string.c

Lines changed: 78 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3007,23 +3007,69 @@ static void php_strtr_array(zval *return_value, zend_string *input, HashTable *p
30073007
}
30083008
/* }}} */
30093009

3010+
/* {{{ count_chars */
3011+
static zend_always_inline zend_long count_chars(const char *p, zend_long length, char ch)
3012+
{
3013+
zend_long count = 0;
3014+
const char *endp;
3015+
3016+
#ifdef __SSE2__
3017+
if (length >= sizeof(__m128i)) {
3018+
__m128i search = _mm_set1_epi8(ch);
3019+
3020+
do {
3021+
__m128i src = _mm_loadu_si128((__m128i*)(p));
3022+
uint32_t mask = _mm_movemask_epi8(_mm_cmpeq_epi8(src, search));
3023+
// TODO: It would be great to use POPCNT, but it's available only with SSE4.1
3024+
#if 1
3025+
while (mask != 0) {
3026+
count++;
3027+
mask = mask & (mask - 1);
3028+
}
3029+
#else
3030+
if (mask) {
3031+
mask = mask - ((mask >> 1) & 0x5555);
3032+
mask = (mask & 0x3333) + ((mask >> 2) & 0x3333);
3033+
mask = (mask + (mask >> 4)) & 0x0F0F;
3034+
mask = (mask + (mask >> 8)) & 0x00ff;
3035+
count += mask;
3036+
}
3037+
#endif
3038+
p += sizeof(__m128i);
3039+
length -= sizeof(__m128i);
3040+
} while (length >= sizeof(__m128i));
3041+
}
3042+
endp = p + length;
3043+
while (p != endp) {
3044+
count += (*p == ch);
3045+
p++;
3046+
}
3047+
#else
3048+
endp = p + length;
3049+
while ((p = memchr(p, ch, endp-p))) {
3050+
count++;
3051+
p++;
3052+
}
3053+
#endif
3054+
return count;
3055+
}
3056+
/* }}} */
3057+
30103058
/* {{{ php_char_to_str_ex */
30113059
static zend_string* php_char_to_str_ex(zend_string *str, char from, char *to, size_t to_len, int case_sensitivity, zend_long *replace_count)
30123060
{
30133061
zend_string *result;
3014-
size_t char_count = 0;
3062+
size_t char_count;
30153063
int lc_from = 0;
3016-
const char *source, *source_end= ZSTR_VAL(str) + ZSTR_LEN(str);
3064+
const char *source, *source_end;
30173065
char *target;
30183066

30193067
if (case_sensitivity) {
3020-
char *p = ZSTR_VAL(str), *e = p + ZSTR_LEN(str);
3021-
while ((p = memchr(p, from, (e - p)))) {
3022-
char_count++;
3023-
p++;
3024-
}
3068+
char_count = count_chars(ZSTR_VAL(str), ZSTR_LEN(str), from);
30253069
} else {
30263070
lc_from = tolower(from);
3071+
char_count = 0;
3072+
source_end = ZSTR_VAL(str) + ZSTR_LEN(str);
30273073
for (source = ZSTR_VAL(str); source < source_end; source++) {
30283074
if (tolower(*source) == lc_from) {
30293075
char_count++;
@@ -3035,6 +3081,10 @@ static zend_string* php_char_to_str_ex(zend_string *str, char from, char *to, si
30353081
return zend_string_copy(str);
30363082
}
30373083

3084+
if (replace_count) {
3085+
*replace_count += char_count;
3086+
}
3087+
30383088
if (to_len > 0) {
30393089
result = zend_string_safe_alloc(char_count, to_len - 1, ZSTR_LEN(str), 0);
30403090
} else {
@@ -3044,27 +3094,24 @@ static zend_string* php_char_to_str_ex(zend_string *str, char from, char *to, si
30443094

30453095
if (case_sensitivity) {
30463096
char *p = ZSTR_VAL(str), *e = p + ZSTR_LEN(str), *s = ZSTR_VAL(str);
3097+
30473098
while ((p = memchr(p, from, (e - p)))) {
30483099
memcpy(target, s, (p - s));
30493100
target += p - s;
30503101
memcpy(target, to, to_len);
30513102
target += to_len;
30523103
p++;
30533104
s = p;
3054-
if (replace_count) {
3055-
*replace_count += 1;
3056-
}
3105+
if (--char_count == 0) break;
30573106
}
30583107
if (s < e) {
30593108
memcpy(target, s, (e - s));
30603109
target += e - s;
30613110
}
30623111
} else {
3112+
source_end = ZSTR_VAL(str) + ZSTR_LEN(str);
30633113
for (source = ZSTR_VAL(str); source < source_end; source++) {
30643114
if (tolower(*source) == lc_from) {
3065-
if (replace_count) {
3066-
*replace_count += 1;
3067-
}
30683115
memcpy(target, to, to_len);
30693116
target += to_len;
30703117
} else {
@@ -5550,10 +5597,9 @@ PHP_FUNCTION(substr_count)
55505597
char *haystack, *needle;
55515598
zend_long offset = 0, length = 0;
55525599
bool length_is_null = 1;
5553-
zend_long count = 0;
5600+
zend_long count;
55545601
size_t haystack_len, needle_len;
55555602
const char *p, *endp;
5556-
char cmp;
55575603

55585604
ZEND_PARSE_PARAMETERS_START(2, 4)
55595605
Z_PARAM_STRING(haystack, haystack_len)
@@ -5569,37 +5615,36 @@ PHP_FUNCTION(substr_count)
55695615
}
55705616

55715617
p = haystack;
5572-
endp = p + haystack_len;
55735618

5574-
if (offset < 0) {
5575-
offset += (zend_long)haystack_len;
5576-
}
5577-
if ((offset < 0) || ((size_t)offset > haystack_len)) {
5578-
zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
5579-
RETURN_THROWS();
5619+
if (offset) {
5620+
if (offset < 0) {
5621+
offset += (zend_long)haystack_len;
5622+
}
5623+
if ((offset < 0) || ((size_t)offset > haystack_len)) {
5624+
zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
5625+
RETURN_THROWS();
5626+
}
5627+
p += offset;
5628+
haystack_len -= offset;
55805629
}
5581-
p += offset;
55825630

55835631
if (!length_is_null) {
5584-
55855632
if (length < 0) {
5586-
length += (haystack_len - offset);
5633+
length += haystack_len;
55875634
}
5588-
if (length < 0 || ((size_t)length > (haystack_len - offset))) {
5635+
if (length < 0 || ((size_t)length > haystack_len)) {
55895636
zend_argument_value_error(4, "must be contained in argument #1 ($haystack)");
55905637
RETURN_THROWS();
55915638
}
5592-
endp = p + length;
5639+
} else {
5640+
length = haystack_len;
55935641
}
55945642

55955643
if (needle_len == 1) {
5596-
cmp = needle[0];
5597-
5598-
while ((p = memchr(p, cmp, endp - p))) {
5599-
count++;
5600-
p++;
5601-
}
5644+
count = count_chars(p, length, needle[0]);
56025645
} else {
5646+
count = 0;
5647+
endp = p + length;
56035648
while ((p = (char*)php_memnstr(p, needle, needle_len, endp))) {
56045649
p += needle_len;
56055650
count++;

0 commit comments

Comments
 (0)