Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions Zend/zend_operators.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)) {
Expand Down
26 changes: 26 additions & 0 deletions benchmark.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

function benchmark_strtoupper(string $str) {
$start = microtime(true);
for ($i = 0; $i < 100000; $i++) {
strtoupper($str);
}
$end = microtime(true);
return $end - $start;
}

$short_ascii = 'abcdefghijklmnopqrstuvwxyz';
$long_ascii = str_repeat($short_ascii, 1000);
$short_mixed = 'aBcDeFgHiJkLmNoPqRsTuVwXyZ';
$long_mixed = str_repeat($short_mixed, 1000);
$short_upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$long_upper = str_repeat($short_upper, 1000);

echo "Benchmarking current strtoupper implementation...\n";

echo "Short ASCII string: " . benchmark_strtoupper($short_ascii) . "s\n";
echo "Long ASCII string: " . benchmark_strtoupper($long_ascii) . "s\n";
echo "Short mixed-case string: " . benchmark_strtoupper($short_mixed) . "s\n";
echo "Long mixed-case string: " . benchmark_strtoupper($long_mixed) . "s\n";
echo "Short uppercase string: " . benchmark_strtoupper($short_upper) . "s\n";
echo "Long uppercase string: " . benchmark_strtoupper($long_upper) . "s\n";
38 changes: 38 additions & 0 deletions benchmark_array_combine.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

function benchmark($name, $func) {
$start = microtime(true);
// Run the function multiple times to get a more stable reading
for ($i = 0; $i < 10; $i++) {
$func();
}
$end = microtime(true);
printf("%-40s: %8.4f seconds\n", $name, ($end - $start));
}

const LARGE_SIZE = 1000000;

// --- Scenario 1: Packed Indexed Arrays (Target for optimization) ---
$indexed_keys = range(0, LARGE_SIZE - 1);
$indexed_values = range(0, LARGE_SIZE - 1);

benchmark("Packed Indexed Arrays", function() use ($indexed_keys, $indexed_values) {
$a = array_combine($indexed_keys, $indexed_values);
});


// --- Scenario 2: Associative Arrays (Check for regressions) ---
$assoc_keys = [];
$assoc_values = [];
for ($i = 0; $i < LARGE_SIZE; $i++) {
$assoc_keys[] = "key_" . $i;
$assoc_values[] = $i;
}

benchmark("Associative Arrays", function() use ($assoc_keys, $assoc_values) {
$a = array_combine($assoc_keys, $assoc_values);
});

echo "\n";

?>
48 changes: 48 additions & 0 deletions benchmark_grapheme_str_split.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

function benchmark($name, $func) {
// A relatively low iteration count because grapheme operations can be intensive.
$iterations = 500;
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$func();
}
$end = microtime(true);
echo str_pad($name, 45) . ": " . number_format($end - $start, 6) . "s\n";
}

echo "Benchmarking current grapheme_str_split implementation...\n";

// A long string (~50KB) mixing ASCII, multi-byte, and complex graphemes.
$long_string = str_repeat("Hello world! Это тест. The quick brown 👨‍👩‍👧‍👦 fox. Á, B́, Ć.", 200);

// ===== TEST CASES =====

// Case 1: Simple ASCII string
benchmark("Simple ASCII string", function() {
grapheme_str_split("abcdefghijklmnopqrstuvwxyz");
});

// Case 2: Multi-byte UTF-8 string (Cyrillic)
benchmark("Multi-byte UTF-8 string (Cyrillic)", function() {
grapheme_str_split("абвгдеёжзийклмнопрстуфхцчшщъыьэюя");
});

// Case 3: Complex Graphemes (Combining Marks)
// 'e' with 3 combining marks is one grapheme
benchmark("Complex Graphemes (Combining Marks)", function() {
grapheme_str_split("é̄̃");
});

// Case 4: Complex Graphemes (Emoji)
// Family emoji and woman with skin tone modifier are single graphemes
benchmark("Complex Graphemes (Emoji)", function() {
grapheme_str_split("👨‍👩‍👧‍👦👩🏽‍💻");
});

// Case 5: Long mixed string
benchmark("Long mixed string", function() use ($long_string) {
grapheme_str_split($long_string);
});

?>
44 changes: 44 additions & 0 deletions benchmark_range.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

function benchmark($name, $func) {
$start = microtime(true);
$func();
$end = microtime(true);
printf("%-40s: %8.4f seconds\n", $name, $end - $start);
}

const LARGE_INT = 10000000;
const LARGE_FLOAT = 10000000.0;

// --- Integer Benchmarks ---
benchmark("Integer Range (1 to 10,000,000)", function() {
$a = range(1, LARGE_INT);
});

benchmark("Integer Range (10,000,000 to 1)", function() {
$a = range(LARGE_INT, 1);
});

benchmark("Integer Range with Step (1 to 10,000,000, step 2)", function() {
$a = range(1, LARGE_INT, 2);
});


// --- Float Benchmarks ---
benchmark("Float Range (1.0 to 10,000,000.0)", function() {
$a = range(1.0, LARGE_FLOAT);
});

benchmark("Float Range with Step (10,000,000.0 to 1.0, step 2.5)", function() {
$a = range(LARGE_FLOAT, 1.0, 2.5);
});


// --- Character Benchmark ---
benchmark("Character Range ('a' to 'z')", function() {
$a = range('a', 'z');
});

echo "\n";

?>
51 changes: 51 additions & 0 deletions benchmark_str_decrement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

function benchmark($name, $func) {
$start = microtime(true);
// Using a smaller iteration count because string operations can be slow
for ($i = 0; $i < 20000; $i++) {
$func();
}
$end = microtime(true);
echo "$name: " . ($end - $start) . "s\n";
}

echo "Benchmarking current str_decrement implementation...\n";

// Case 1: Simple numeric string, no borrow needed.
$s1 = "123456789123456789";
benchmark("Simple numeric string", function() use ($s1) {
str_decrement($s1);
});

// Case 2: Long numeric string with full borrow. This is the key case for my optimization.
$s2 = "1" . str_repeat("0", 50);
benchmark("Long numeric string with full borrow", function() use ($s2) {
str_decrement($s2);
});

// Case 3: Simple alphanumeric string, no borrow needed.
$s3 = "abcdefg9";
benchmark("Simple alphanumeric string", function() use ($s3) {
str_decrement($s3);
});

// Case 4: Long alphanumeric string with numeric borrow.
$s4 = "b" . str_repeat("0", 50);
benchmark("Long alphanumeric with numeric borrow", function() use ($s4) {
str_decrement($s4);
});

// Case 5: Long alphanumeric string with letter borrow.
$s5 = "z" . str_repeat("a", 50);
benchmark("Long alphanumeric with letter borrow", function() use ($s5) {
str_decrement($s5);
});

// Case 6: A long string that does not trigger the leading-zero optimization.
$s6 = "2" . str_repeat("0", 50);
benchmark("Long numeric string without leading-zero result", function() use ($s6) {
str_decrement($s6);
});

?>
49 changes: 49 additions & 0 deletions benchmark_str_ends_with.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

function benchmark($name, $func) {
$start = microtime(true);
// This function is very fast, so a high iteration count is needed.
for ($i = 0; $i < 500000; $i++) {
$func();
}
$end = microtime(true);
echo str_pad($name, 40) . ": " . number_format($end - $start, 6) . "s\n";
}

echo "Benchmarking current str_ends_with implementation...\n";

$long_haystack = "This is a very long string that is used for benchmarking purposes to see how the function performs with a significant amount of data to process.";
$long_needle_match = "a significant amount of data to process.";
$long_needle_no_match = "a significant amount of data to process!";

// Case 1: Haystack shorter than needle (fast path)
benchmark("Haystack shorter than needle", function() {
str_ends_with("short", "this is much longer");
});

// Case 2: Matching short needle
benchmark("Matching short needle", function() use ($long_haystack) {
str_ends_with($long_haystack, "process.");
});

// Case 3: Non-matching short needle
benchmark("Non-matching short needle", function() use ($long_haystack) {
str_ends_with($long_haystack, "process!");
});

// Case 4: Matching long needle
benchmark("Matching long needle", function() use ($long_haystack, $long_needle_match) {
str_ends_with($long_haystack, $long_needle_match);
});

// Case 5: Non-matching long needle
benchmark("Non-matching long needle", function() use ($long_haystack, $long_needle_no_match) {
str_ends_with($long_haystack, $long_needle_no_match);
});

// Case 6: Matching empty needle (edge case)
benchmark("Matching empty needle", function() use ($long_haystack) {
str_ends_with($long_haystack, "");
});

?>
50 changes: 50 additions & 0 deletions benchmark_str_increment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

function benchmark($name, $func) {
$start = microtime(true);
for ($i = 0; $i < 20000; $i++) {
$func();
}
$end = microtime(true);
echo "$name: " . ($end - $start) . "s\n";
}

echo "Benchmarking current str_increment implementation...\n";

// Case 1: Simple numeric string, no carry needed.
$s1 = "123456788";
benchmark("Simple numeric string", function() use ($s1) {
str_increment($s1);
});

// Case 2: Long numeric string with full carry. This is the key case for my optimization.
$s2 = str_repeat("9", 50);
benchmark("Long numeric string with full carry", function() use ($s2) {
str_increment($s2);
});

// Case 3: Simple alphanumeric string, no carry needed.
$s3 = "abcde8";
benchmark("Simple alphanumeric string", function() use ($s3) {
str_increment($s3);
});

// Case 4: Long alphanumeric string with numeric carry.
$s4 = "a" . str_repeat("9", 50);
benchmark("Long alphanumeric with numeric carry", function() use ($s4) {
str_increment($s4);
});

// Case 5: Long alphanumeric string with full letter and numeric carry.
$s5 = "z" . str_repeat("9", 50);
benchmark("Long alphanumeric with full carry", function() use ($s5) {
str_increment($s5);
});

// Case 6: A long string that does not trigger reallocation.
$s6 = "8" . str_repeat("9", 50);
benchmark("Long numeric string without reallocation", function() use ($s6) {
str_increment($s6);
});

?>
Loading
Loading