Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
e2998b0
#50: libuv_reactor.c optimize libuv_reactor_execute
EdmondDantes Jul 20, 2025
ea50058
#50: optimize circular_buffer.c
EdmondDantes Jul 20, 2025
521afe4
#50: + Fiber pool
EdmondDantes Jul 20, 2025
5feebe7
#50: + http_server_coroutines.php
EdmondDantes Jul 20, 2025
918d5aa
#48: Key Changes:
EdmondDantes Jul 20, 2025
124d2c5
% refactoring scheduler logic for Fiber pool
EdmondDantes Jul 21, 2025
031b6c0
% refactoring scheduler logic for Fiber pool2
EdmondDantes Jul 21, 2025
b69ea23
% refactoring scheduler logic for Fiber pool3
EdmondDantes Jul 21, 2025
c682ff9
% refactoring scheduler logic for Fiber pool4
EdmondDantes Jul 22, 2025
5031e23
% refactoring scheduler: code reorganizing
EdmondDantes Jul 22, 2025
0be28d4
#50: Refactoring scheduler.c
EdmondDantes Jul 22, 2025
a35ee87
#50: Embed waker in coroutine structure to avoid malloc overhead
EdmondDantes Jul 22, 2025
24ac655
#50: 1. Embedded waker in coroutine structure (coroutine.h:52):
EdmondDantes Jul 22, 2025
690fbd2
#50: Reorganize coroutine.c: group PHP methods together and add forwa…
EdmondDantes Jul 22, 2025
fa60a40
#50: Refactoring of the Waker object, which is no longer destroyed an…
EdmondDantes Jul 22, 2025
f9e4217
#50: * fix bugs
EdmondDantes Jul 22, 2025
27060f4
#50: add ZEND_ASYNC_WAKER_NOT_IN_QUEUE
EdmondDantes Jul 23, 2025
76d76c6
#50: refactoring execute_next_coroutine
EdmondDantes Jul 23, 2025
8a1be0a
#50: * fix memory leaks
EdmondDantes Jul 23, 2025
46015df
#50: * fix suspend\001-suspend_basic
EdmondDantes Jul 23, 2025
db286fd
#50: * fix suspend tests
EdmondDantes Jul 23, 2025
259bc85
#50: + zend_async_waker_clean
EdmondDantes Jul 23, 2025
2e92301
#50: * Fixed the behavior of suspend, which is now responsible for tr…
EdmondDantes Jul 23, 2025
cb7dbf4
* fix await\008-awaitFirstSuccess_basic.php
EdmondDantes Aug 24, 2025
cd873f9
* fix for 031-awaitAllOrFail_with_interruption
EdmondDantes Aug 24, 2025
246d628
* fix for 031-awaitAllOrFail_with_interruption.php
EdmondDantes Aug 29, 2025
4940dce
* fix for await\044-awaitAllOrFail_empty_iterable.php
EdmondDantes Aug 29, 2025
c120cc6
* fix for await\066-await_cancelled_coroutine.php
EdmondDantes Aug 30, 2025
ebdc285
+ FIBER_DEBUG
EdmondDantes Aug 31, 2025
775462d
+ FIBER_DEBUG2
EdmondDantes Aug 31, 2025
0f252dd
* Fixed a bug with WAKER cleanup after the ZEND_ASYNC_SUSPEND() opera…
EdmondDantes Aug 31, 2025
e6fd86d
* Fix bailout\006-memory-exhaustion-multiple-coroutines.php and 001 t…
EdmondDantes Sep 1, 2025
5998397
* fix bailout tests and function switch_to_scheduler issue when trans…
EdmondDantes Sep 1, 2025
71a6946
* Fixed a bug with cyclic coroutine context switching in fiber_pool_c…
EdmondDantes Sep 1, 2025
428df99
* fix memory leak for await 001 test
EdmondDantes Sep 1, 2025
28a0186
* fix memory leak for await tests
EdmondDantes Sep 1, 2025
81de257
* fix memory leak for await tests
EdmondDantes Sep 1, 2025
74e5e61
* fix coroutine.result refcount
EdmondDantes Sep 2, 2025
cf39028
* fix code for coroutine/035-coroutine_deep_recursion.phpt
EdmondDantes Sep 2, 2025
ca24445
* fix code for coroutine/035-coroutine_deep_recursion.phpt
EdmondDantes Sep 2, 2025
22fbf2c
* Fix invalid read in fiber cleanup by clearing VM stack
EdmondDantes Sep 2, 2025
ca2e5d2
#50 * Fix invalid read in fiber cleanup by clearing VM stack
EdmondDantes Sep 2, 2025
0811bb5
Merge remote-tracking branch 'origin/50-performance-optimizations' in…
EdmondDantes Sep 2, 2025
ecd3496
#50: * fix test edge_cases/001-deadlock-basic-test
EdmondDantes Sep 3, 2025
843720b
#50: remove unused code
EdmondDantes Sep 3, 2025
13cff19
#50: * fix AddressSanitizer stack issue for char vm_stack_memory[ZEND…
EdmondDantes Sep 3, 2025
72c5727
#50: * fix bailout tests for PHP JIT.
EdmondDantes Sep 4, 2025
07b9eb6
#50: fix run-tests.sh
EdmondDantes Sep 4, 2025
c55b2cc
#50: fix JIT PHP issue with opcache.jit_hot_func=1 and bailout tests
EdmondDantes Sep 4, 2025
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
6 changes: 4 additions & 2 deletions async.c
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ PHP_FUNCTION(Async_suspend)
THROW_IF_SCHEDULER_CONTEXT;
ZEND_ASYNC_ENQUEUE_COROUTINE(ZEND_ASYNC_CURRENT_COROUTINE);
ZEND_ASYNC_SUSPEND();
zend_async_waker_clean(ZEND_ASYNC_CURRENT_COROUTINE);
}

PHP_FUNCTION(Async_protect)
Expand Down Expand Up @@ -277,6 +278,7 @@ PHP_FUNCTION(Async_await)
ZEND_ASYNC_SUSPEND();

if (UNEXPECTED(EG(exception) != NULL)) {
zend_async_waker_clean(coroutine);
RETURN_THROWS();
}

Expand All @@ -288,7 +290,7 @@ PHP_FUNCTION(Async_await)
ZVAL_COPY(return_value, &coroutine->waker->result);
}

zend_async_waker_destroy(coroutine);
zend_async_waker_clean(coroutine);
}

PHP_FUNCTION(Async_awaitAnyOrFail)
Expand Down Expand Up @@ -609,7 +611,7 @@ PHP_FUNCTION(Async_delay)

ZEND_ASYNC_SUSPEND();

zend_async_waker_destroy(coroutine);
zend_async_waker_clean(coroutine);
}

PHP_FUNCTION(Async_timeout)
Expand Down
2 changes: 2 additions & 0 deletions async_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,7 @@ static void async_cancel_awaited_futures(async_await_context_t *await_context, H
}

ZEND_ASYNC_SUSPEND();
zend_async_waker_clean(ZEND_ASYNC_CURRENT_COROUTINE);
}

/**
Expand Down Expand Up @@ -1044,6 +1045,7 @@ void async_await_futures(zval *iterable,

if (coroutine->waker->events.nNumOfElements > 0) {
ZEND_ASYNC_SUSPEND();
zend_async_waker_clean(ZEND_ASYNC_CURRENT_COROUTINE);
}

// If the await on futures has completed and
Expand Down
144 changes: 144 additions & 0 deletions benchmarks/compare_results.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php
/**
* Comparison script for Coroutines vs Fibers benchmarks
* Run both benchmarks and compare results
*/

// Increase memory limit for benchmark
ini_set('memory_limit', '512M');

echo "=== Coroutines vs Fibers Comparison ===\n\n";

// Configuration
$iterations = 1000;
$switches = 50;

echo "Configuration:\n";
echo "- Iterations: $iterations\n";
echo "- Switches per iteration: $switches\n";
echo "- Total context switches: " . ($iterations * $switches) . "\n\n";

// Function to run external benchmark and capture output
function runBenchmark($script) {
$output = [];
$return_var = 0;
exec("php " . __DIR__ . "/$script 2>&1", $output, $return_var);
return [
'output' => implode("\n", $output),
'success' => $return_var === 0
];
}

// Function to parse benchmark results from output
function parseResults($output) {
$results = [];

if (preg_match('/Time: ([0-9.]+) seconds/', $output, $matches)) {
$results['time'] = (float)$matches[1];
}

if (preg_match('/Switches per second: ([0-9,]+)/', $output, $matches)) {
$results['switches_per_sec'] = (int)str_replace(',', '', $matches[1]);
}

if (preg_match('/Overhead per switch: ([0-9.]+) μs/', $output, $matches)) {
$results['overhead_us'] = (float)$matches[1];
}

if (preg_match('/Used for benchmark: ([0-9.]+) MB/', $output, $matches)) {
$results['memory_mb'] = (float)$matches[1];
}

return $results;
}

echo "Running coroutines benchmark...\n";
$coroutineResult = runBenchmark('coroutines_benchmark.php');

echo "Running fibers benchmark...\n";
$fiberResult = runBenchmark('fibers_benchmark.php');

echo "\n" . str_repeat("=", 60) . "\n";
echo "COMPARISON RESULTS\n";
echo str_repeat("=", 60) . "\n\n";

if (!$coroutineResult['success']) {
echo "❌ Coroutines benchmark failed:\n";
echo $coroutineResult['output'] . "\n\n";
} else {
echo "✅ Coroutines benchmark completed successfully\n\n";
}

if (!$fiberResult['success']) {
echo "❌ Fibers benchmark failed:\n";
echo $fiberResult['output'] . "\n\n";
} else {
echo "✅ Fibers benchmark completed successfully\n\n";
}

// Parse and compare results if both succeeded
if ($coroutineResult['success'] && $fiberResult['success']) {
$coroutineStats = parseResults($coroutineResult['output']);
$fiberStats = parseResults($fiberResult['output']);

echo "📊 PERFORMANCE COMPARISON:\n\n";

// Time comparison
if (isset($coroutineStats['time']) && isset($fiberStats['time'])) {
$timeRatio = $fiberStats['time'] / $coroutineStats['time'];
echo "⏱️ Execution Time:\n";
echo " Coroutines: " . number_format($coroutineStats['time'], 4) . "s\n";
echo " Fibers: " . number_format($fiberStats['time'], 4) . "s\n";
if ($timeRatio > 1) {
echo " 🏆 Coroutines are " . number_format($timeRatio, 2) . "x faster\n\n";
} else {
echo " 🏆 Fibers are " . number_format(1/$timeRatio, 2) . "x faster\n\n";
}
}

// Throughput comparison
if (isset($coroutineStats['switches_per_sec']) && isset($fiberStats['switches_per_sec'])) {
echo "🚀 Throughput (switches/sec):\n";
echo " Coroutines: " . number_format($coroutineStats['switches_per_sec']) . "\n";
echo " Fibers: " . number_format($fiberStats['switches_per_sec']) . "\n";
$throughputRatio = $coroutineStats['switches_per_sec'] / $fiberStats['switches_per_sec'];
if ($throughputRatio > 1) {
echo " 🏆 Coroutines have " . number_format($throughputRatio, 2) . "x higher throughput\n\n";
} else {
echo " 🏆 Fibers have " . number_format(1/$throughputRatio, 2) . "x higher throughput\n\n";
}
}

// Overhead comparison
if (isset($coroutineStats['overhead_us']) && isset($fiberStats['overhead_us'])) {
echo "⚡ Overhead per switch:\n";
echo " Coroutines: " . number_format($coroutineStats['overhead_us'], 2) . " μs\n";
echo " Fibers: " . number_format($fiberStats['overhead_us'], 2) . " μs\n";
$overheadRatio = $fiberStats['overhead_us'] / $coroutineStats['overhead_us'];
if ($overheadRatio > 1) {
echo " 🏆 Coroutines have " . number_format($overheadRatio, 2) . "x lower overhead\n\n";
} else {
echo " 🏆 Fibers have " . number_format(1/$overheadRatio, 2) . "x lower overhead\n\n";
}
}

// Memory comparison
if (isset($coroutineStats['memory_mb']) && isset($fiberStats['memory_mb'])) {
echo "💾 Memory Usage:\n";
echo " Coroutines: " . number_format($coroutineStats['memory_mb'], 2) . " MB\n";
echo " Fibers: " . number_format($fiberStats['memory_mb'], 2) . " MB\n";
$memoryRatio = $fiberStats['memory_mb'] / $coroutineStats['memory_mb'];
if ($memoryRatio > 1) {
echo " 🏆 Coroutines use " . number_format($memoryRatio, 2) . "x less memory\n\n";
} else {
echo " 🏆 Fibers use " . number_format(1/$memoryRatio, 2) . "x less memory\n\n";
}
}
} else {
echo "⚠️ Cannot compare results - one or both benchmarks failed\n";
}

echo "💡 Note: Results may vary based on system load and configuration\n";
echo "💡 Run multiple times and average results for production comparisons\n";

echo "\nComparison completed.\n";
102 changes: 102 additions & 0 deletions benchmarks/coroutines_benchmark.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php
/**
* Benchmark: Async Coroutines switching performance
* Tests the performance of async coroutines context switching
*/

// Increase memory limit for benchmark
ini_set('memory_limit', '512M');

use function Async\spawn;
use function Async\awaitAll;

echo "=== Async Coroutines Benchmark ===\n\n";

// Test configuration
$iterations = 1000;
$switches = 1000;

// Benchmark async coroutines
function benchmarkCoroutines($iterations, $switches) {
$start = microtime(true);
$memoryBeforeCreate = getCurrentMemoryUsage();

$coroutines = [];
for ($i = 0; $i < $iterations; $i++) {
$coroutines[] = spawn(function() use ($switches) {
for ($j = 0; $j < $switches; $j++) {
// Yield control to other coroutines
Async\suspend();
}
});
}

$memoryAfterCreate = getCurrentMemoryUsage();

awaitAll($coroutines);

$end = microtime(true);
return [
'time' => $end - $start,
'memoryBeforeCreate' => $memoryBeforeCreate,
'memoryAfterCreate' => $memoryAfterCreate,
'creationOverhead' => $memoryAfterCreate - $memoryBeforeCreate
];
}

// Memory usage tracking
function getCurrentMemoryUsage() {
return memory_get_usage(true);
}

function getPeakMemoryUsage() {
return memory_get_peak_usage(true);
}

// Run benchmark
echo "Configuration:\n";
echo "- Iterations: $iterations\n";
echo "- Switches per iteration: $switches\n";
echo "- Total context switches: " . ($iterations * $switches) . "\n\n";

// Memory usage before benchmark
$memoryBefore = getCurrentMemoryUsage();

// Warmup
echo "Warming up...\n";
benchmarkCoroutines(100, 10);

echo "\nRunning coroutines benchmark...\n";

// Benchmark coroutines
$result = benchmarkCoroutines($iterations, $switches);
$coroutineTime = $result['time'];

// Memory usage after benchmark
$memoryAfter = getCurrentMemoryUsage();
$memoryPeak = getPeakMemoryUsage();

// Results
echo "\n=== Results ===\n";
echo "Time: " . number_format($coroutineTime, 4) . " seconds\n";
echo "Switches per second: " . number_format(($iterations * $switches) / $coroutineTime, 0) . "\n";
echo "Overhead per switch: " . number_format(($coroutineTime / ($iterations * $switches)) * 1000000, 2) . " μs\n";

echo "\nMemory Usage:\n";
echo "Before: " . number_format($memoryBefore / 1024 / 1024, 2) . " MB\n";
echo "After creation: " . number_format($result['memoryAfterCreate'] / 1024 / 1024, 2) . " MB\n";
echo "After completion: " . number_format($memoryAfter / 1024 / 1024, 2) . " MB\n";
echo "Peak: " . number_format($memoryPeak / 1024 / 1024, 2) . " MB\n";
echo "Creation overhead: " . number_format($result['creationOverhead'] / 1024 / 1024, 2) . " MB\n";
echo "Used for benchmark: " . number_format(($memoryAfter - $memoryBefore) / 1024 / 1024, 2) . " MB\n";

// Additional metrics
$totalSwitches = $iterations * $switches;
echo "\nPerformance Metrics:\n";
echo "Total coroutines created: $iterations\n";
echo "Total context switches: $totalSwitches\n";
echo "Average time per coroutine: " . number_format($coroutineTime / $iterations * 1000, 2) . " ms\n";
echo "Memory per coroutine (creation): " . number_format($result['creationOverhead'] / $iterations, 0) . " bytes\n";
echo "Memory per coroutine (total): " . number_format(($memoryAfter - $memoryBefore) / $iterations, 0) . " bytes\n";

echo "\nCoroutines benchmark completed.\n";
Loading
Loading