diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 957d93f984967..7e3e5fe38146b 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -3725,6 +3725,40 @@ static zend_always_inline void zend_memnstr_ex_pre(unsigned int td[], const char ZEND_API const char* ZEND_FASTCALL zend_memnstr_ex(const char *haystack, const char *needle, size_t needle_len, const char *end) /* {{{ */ { +#if defined(__SSE2__) + if (needle_len >= 4) { + const char *p; + __m128i first_chars = _mm_set1_epi8(needle[0]); + __m128i last_chars = _mm_set1_epi8(needle[needle_len - 1]); + + for (p = haystack; p <= end - needle_len; p += 16) { + __m128i haystack_first_chars = _mm_loadu_si128((__m128i*)p); + __m128i haystack_last_chars = _mm_loadu_si128((__m128i*)(p + needle_len - 1)); + __m128i eq_first = _mm_cmpeq_epi8(first_chars, haystack_first_chars); + __m128i eq_last = _mm_cmpeq_epi8(last_chars, haystack_last_chars); + unsigned long mask = _mm_movemask_epi8(_mm_and_si128(eq_first, eq_last)); + + while (mask > 0) { + unsigned long bit = 1UL << __builtin_ctzl(mask); + size_t ofs = __builtin_ctzl(mask); + if (memcmp(p + ofs + 1, needle + 1, needle_len - 2) == 0) { + return p + ofs; + } + mask &= ~bit; + } + } + + // Handle the remainder of the string if its length is not a multiple of 16 + for (; p <= end - needle_len; p++) { + if (p[0] == needle[0] && memcmp(p + 1, needle + 1, needle_len - 1) == 0) { + return p; + } + } + + return NULL; + } +#endif + unsigned int td[256]; size_t i; const char *p; @@ -3739,12 +3773,7 @@ ZEND_API const char* ZEND_FASTCALL zend_memnstr_ex(const char *haystack, const c end -= needle_len; while (p <= end) { - for (i = 0; i < needle_len; i++) { - if (needle[i] != p[i]) { - break; - } - } - if (i == needle_len) { + if (memcmp(p, needle, needle_len) == 0) { return p; } if (UNEXPECTED(p == end)) { diff --git a/benchmark.php b/benchmark.php new file mode 100644 index 0000000000000..ad1ee62ab91d8 --- /dev/null +++ b/benchmark.php @@ -0,0 +1,26 @@ + diff --git a/benchmark_grapheme_str_split.php b/benchmark_grapheme_str_split.php new file mode 100644 index 0000000000000..1a047d28f34aa --- /dev/null +++ b/benchmark_grapheme_str_split.php @@ -0,0 +1,48 @@ + diff --git a/benchmark_range.php b/benchmark_range.php new file mode 100644 index 0000000000000..243198686dfa6 --- /dev/null +++ b/benchmark_range.php @@ -0,0 +1,44 @@ + diff --git a/benchmark_str_decrement.php b/benchmark_str_decrement.php new file mode 100644 index 0000000000000..348a851c59d89 --- /dev/null +++ b/benchmark_str_decrement.php @@ -0,0 +1,51 @@ + diff --git a/benchmark_str_ends_with.php b/benchmark_str_ends_with.php new file mode 100644 index 0000000000000..f9c02d30cb3a4 --- /dev/null +++ b/benchmark_str_ends_with.php @@ -0,0 +1,49 @@ + diff --git a/benchmark_str_increment.php b/benchmark_str_increment.php new file mode 100644 index 0000000000000..7d85cfcdad1aa --- /dev/null +++ b/benchmark_str_increment.php @@ -0,0 +1,50 @@ + diff --git a/benchmark_str_ireplace.php b/benchmark_str_ireplace.php new file mode 100644 index 0000000000000..8157506eb2a03 --- /dev/null +++ b/benchmark_str_ireplace.php @@ -0,0 +1,47 @@ + diff --git a/benchmark_str_pad.php b/benchmark_str_pad.php new file mode 100644 index 0000000000000..64612216045e3 --- /dev/null +++ b/benchmark_str_pad.php @@ -0,0 +1,27 @@ + diff --git a/benchmark_strstr.php b/benchmark_strstr.php new file mode 100644 index 0000000000000..ce361dc51b5f2 --- /dev/null +++ b/benchmark_strstr.php @@ -0,0 +1,23 @@ += end_double; ++i, element = start_double - (i * step_double)) { + for (i = 0; i < size; i++) { ZEND_HASH_FILL_SET_DOUBLE(element); ZEND_HASH_FILL_NEXT(); + element -= step_double; } } ZEND_HASH_FILL_END(); } else if (end_double > start_double) { /* Increasing float range */ @@ -3132,10 +3134,12 @@ PHP_FUNCTION(range) RANGE_CHECK_DOUBLE_INIT_ARRAY(end_double, start_double, step_double); + element = start_double; ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { - for (i = 0, element = start_double; i < size && element <= end_double; ++i, element = start_double + (i * step_double)) { + for (i = 0; i < size; i++) { ZEND_HASH_FILL_SET_DOUBLE(element); ZEND_HASH_FILL_NEXT(); + element += step_double; } } ZEND_HASH_FILL_END(); } else { @@ -3146,8 +3150,9 @@ PHP_FUNCTION(range) } else { ZEND_ASSERT(start_type == IS_LONG && end_type == IS_LONG && !is_step_double); /* unsigned_step is a zend_ulong so that comparisons to it don't overflow, i.e. low - high < lstep */ - zend_ulong unsigned_step= (zend_ulong)step; + zend_ulong unsigned_step = (zend_ulong)step; uint32_t i, size; + zend_long current_val; /* Decreasing int range */ if (start_long > end_long) { @@ -3157,10 +3162,12 @@ PHP_FUNCTION(range) RANGE_CHECK_LONG_INIT_ARRAY(start_long, end_long, unsigned_step); + current_val = start_long; ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { for (i = 0; i < size; ++i) { - ZEND_HASH_FILL_SET_LONG(start_long - (i * unsigned_step)); + ZEND_HASH_FILL_SET_LONG(current_val); ZEND_HASH_FILL_NEXT(); + current_val -= unsigned_step; } } ZEND_HASH_FILL_END(); } else if (end_long > start_long) { /* Increasing int range */ @@ -3173,10 +3180,12 @@ PHP_FUNCTION(range) RANGE_CHECK_LONG_INIT_ARRAY(end_long, start_long, unsigned_step); + current_val = start_long; ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { for (i = 0; i < size; ++i) { - ZEND_HASH_FILL_SET_LONG(start_long + (i * unsigned_step)); + ZEND_HASH_FILL_SET_LONG(current_val); ZEND_HASH_FILL_NEXT(); + current_val += unsigned_step; } } ZEND_HASH_FILL_END(); } else { @@ -7083,7 +7092,7 @@ PHP_FUNCTION(array_combine) HashTable *values, *keys; uint32_t pos_values = 0; zval *entry_keys, *entry_values; - int num_keys, num_values; + uint32_t num_keys, num_values; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_ARRAY_HT(keys) @@ -7103,29 +7112,48 @@ PHP_FUNCTION(array_combine) } array_init_size(return_value, num_keys); - ZEND_HASH_FOREACH_VAL(keys, entry_keys) { - while (1) { - if (pos_values >= values->nNumUsed) { - break; + + /* Fast path for packed arrays without holes */ + if (HT_IS_PACKED(keys) && HT_IS_WITHOUT_HOLES(keys) && + HT_IS_PACKED(values) && HT_IS_WITHOUT_HOLES(values)) { + uint32_t i; + for (i = 0; i < num_keys; i++) { + entry_keys = &keys->arPacked[i]; + entry_values = &values->arPacked[i]; + + if (Z_TYPE_P(entry_keys) == IS_LONG) { + zend_hash_index_update(Z_ARRVAL_P(return_value), Z_LVAL_P(entry_keys), entry_values); + } else { + zend_string *tmp_key; + zend_string *key = zval_get_tmp_string(entry_keys, &tmp_key); + zend_symtable_update(Z_ARRVAL_P(return_value), key, entry_values); + zend_tmp_string_release(tmp_key); } - entry_values = ZEND_HASH_ELEMENT(values, pos_values); - if (Z_TYPE_P(entry_values) != IS_UNDEF) { - if (Z_TYPE_P(entry_keys) == IS_LONG) { - entry_values = zend_hash_index_update(Z_ARRVAL_P(return_value), - Z_LVAL_P(entry_keys), entry_values); - } else { - zend_string *tmp_key; - zend_string *key = zval_get_tmp_string(entry_keys, &tmp_key); - entry_values = zend_symtable_update(Z_ARRVAL_P(return_value), - key, entry_values); - zend_tmp_string_release(tmp_key); + } + } else { + ZEND_HASH_FOREACH_VAL(keys, entry_keys) { + while (1) { + if (pos_values >= values->nNumUsed) { + break; + } + entry_values = ZEND_HASH_ELEMENT(values, pos_values); + if (Z_TYPE_P(entry_values) != IS_UNDEF) { + if (Z_TYPE_P(entry_keys) == IS_LONG) { + zend_hash_index_update(Z_ARRVAL_P(return_value), + Z_LVAL_P(entry_keys), entry_values); + } else { + zend_string *tmp_key; + zend_string *key = zval_get_tmp_string(entry_keys, &tmp_key); + zend_symtable_update(Z_ARRVAL_P(return_value), + key, entry_values); + zend_tmp_string_release(tmp_key); + } + pos_values++; + break; } - zval_add_ref(entry_values); pos_values++; - break; } - pos_values++; - } - } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); + } } /* }}} */ diff --git a/ext/standard/string.c b/ext/standard/string.c index a5acb28ddb3bc..a5c2886930ee2 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -5805,16 +5805,42 @@ PHP_FUNCTION(str_pad) } /* First we pad on the left. */ - for (i = 0; i < left_pad; i++) - ZSTR_VAL(result)[ZSTR_LEN(result)++] = pad_str[i % pad_str_len]; + if (left_pad > 0) { + if (pad_str_len == 1) { + memset(ZSTR_VAL(result), *pad_str, left_pad); + } else { + char *p = ZSTR_VAL(result); + for (i = 0; i < left_pad / pad_str_len; i++) { + memcpy(p, pad_str, pad_str_len); + p += pad_str_len; + } + if (left_pad % pad_str_len) { + memcpy(p, pad_str, left_pad % pad_str_len); + } + } + ZSTR_LEN(result) += left_pad; + } /* Then we copy the input string. */ memcpy(ZSTR_VAL(result) + ZSTR_LEN(result), ZSTR_VAL(input), ZSTR_LEN(input)); ZSTR_LEN(result) += ZSTR_LEN(input); /* Finally, we pad on the right. */ - for (i = 0; i < right_pad; i++) - ZSTR_VAL(result)[ZSTR_LEN(result)++] = pad_str[i % pad_str_len]; + if (right_pad > 0) { + if (pad_str_len == 1) { + memset(ZSTR_VAL(result) + ZSTR_LEN(result), *pad_str, right_pad); + } else { + char *p = ZSTR_VAL(result) + ZSTR_LEN(result); + for (i = 0; i < right_pad / pad_str_len; i++) { + memcpy(p, pad_str, pad_str_len); + p += pad_str_len; + } + if (right_pad % pad_str_len) { + memcpy(p, pad_str, right_pad % pad_str_len); + } + } + ZSTR_LEN(result) += right_pad; + } ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0';