Skip to content

Commit 1d5faf6

Browse files
authored
Merge pull request #52 from true-async/50-performance-optimizations
50 performance optimizations
2 parents 3410c1b + c55b2cc commit 1d5faf6

23 files changed

+2224
-1289
lines changed

async.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ PHP_FUNCTION(Async_suspend)
141141
THROW_IF_SCHEDULER_CONTEXT;
142142
ZEND_ASYNC_ENQUEUE_COROUTINE(ZEND_ASYNC_CURRENT_COROUTINE);
143143
ZEND_ASYNC_SUSPEND();
144+
zend_async_waker_clean(ZEND_ASYNC_CURRENT_COROUTINE);
144145
}
145146

146147
PHP_FUNCTION(Async_protect)
@@ -277,6 +278,7 @@ PHP_FUNCTION(Async_await)
277278
ZEND_ASYNC_SUSPEND();
278279

279280
if (UNEXPECTED(EG(exception) != NULL)) {
281+
zend_async_waker_clean(coroutine);
280282
RETURN_THROWS();
281283
}
282284

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

291-
zend_async_waker_destroy(coroutine);
293+
zend_async_waker_clean(coroutine);
292294
}
293295

294296
PHP_FUNCTION(Async_awaitAnyOrFail)
@@ -609,7 +611,7 @@ PHP_FUNCTION(Async_delay)
609611

610612
ZEND_ASYNC_SUSPEND();
611613

612-
zend_async_waker_destroy(coroutine);
614+
zend_async_waker_clean(coroutine);
613615
}
614616

615617
PHP_FUNCTION(Async_timeout)

async_API.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,7 @@ static void async_cancel_awaited_futures(async_await_context_t *await_context, H
824824
}
825825

826826
ZEND_ASYNC_SUSPEND();
827+
zend_async_waker_clean(ZEND_ASYNC_CURRENT_COROUTINE);
827828
}
828829

829830
/**
@@ -1044,6 +1045,7 @@ void async_await_futures(zval *iterable,
10441045

10451046
if (coroutine->waker->events.nNumOfElements > 0) {
10461047
ZEND_ASYNC_SUSPEND();
1048+
zend_async_waker_clean(ZEND_ASYNC_CURRENT_COROUTINE);
10471049
}
10481050

10491051
// If the await on futures has completed and

benchmarks/compare_results.php

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
/**
3+
* Comparison script for Coroutines vs Fibers benchmarks
4+
* Run both benchmarks and compare results
5+
*/
6+
7+
// Increase memory limit for benchmark
8+
ini_set('memory_limit', '512M');
9+
10+
echo "=== Coroutines vs Fibers Comparison ===\n\n";
11+
12+
// Configuration
13+
$iterations = 1000;
14+
$switches = 50;
15+
16+
echo "Configuration:\n";
17+
echo "- Iterations: $iterations\n";
18+
echo "- Switches per iteration: $switches\n";
19+
echo "- Total context switches: " . ($iterations * $switches) . "\n\n";
20+
21+
// Function to run external benchmark and capture output
22+
function runBenchmark($script) {
23+
$output = [];
24+
$return_var = 0;
25+
exec("php " . __DIR__ . "/$script 2>&1", $output, $return_var);
26+
return [
27+
'output' => implode("\n", $output),
28+
'success' => $return_var === 0
29+
];
30+
}
31+
32+
// Function to parse benchmark results from output
33+
function parseResults($output) {
34+
$results = [];
35+
36+
if (preg_match('/Time: ([0-9.]+) seconds/', $output, $matches)) {
37+
$results['time'] = (float)$matches[1];
38+
}
39+
40+
if (preg_match('/Switches per second: ([0-9,]+)/', $output, $matches)) {
41+
$results['switches_per_sec'] = (int)str_replace(',', '', $matches[1]);
42+
}
43+
44+
if (preg_match('/Overhead per switch: ([0-9.]+) μs/', $output, $matches)) {
45+
$results['overhead_us'] = (float)$matches[1];
46+
}
47+
48+
if (preg_match('/Used for benchmark: ([0-9.]+) MB/', $output, $matches)) {
49+
$results['memory_mb'] = (float)$matches[1];
50+
}
51+
52+
return $results;
53+
}
54+
55+
echo "Running coroutines benchmark...\n";
56+
$coroutineResult = runBenchmark('coroutines_benchmark.php');
57+
58+
echo "Running fibers benchmark...\n";
59+
$fiberResult = runBenchmark('fibers_benchmark.php');
60+
61+
echo "\n" . str_repeat("=", 60) . "\n";
62+
echo "COMPARISON RESULTS\n";
63+
echo str_repeat("=", 60) . "\n\n";
64+
65+
if (!$coroutineResult['success']) {
66+
echo "❌ Coroutines benchmark failed:\n";
67+
echo $coroutineResult['output'] . "\n\n";
68+
} else {
69+
echo "✅ Coroutines benchmark completed successfully\n\n";
70+
}
71+
72+
if (!$fiberResult['success']) {
73+
echo "❌ Fibers benchmark failed:\n";
74+
echo $fiberResult['output'] . "\n\n";
75+
} else {
76+
echo "✅ Fibers benchmark completed successfully\n\n";
77+
}
78+
79+
// Parse and compare results if both succeeded
80+
if ($coroutineResult['success'] && $fiberResult['success']) {
81+
$coroutineStats = parseResults($coroutineResult['output']);
82+
$fiberStats = parseResults($fiberResult['output']);
83+
84+
echo "📊 PERFORMANCE COMPARISON:\n\n";
85+
86+
// Time comparison
87+
if (isset($coroutineStats['time']) && isset($fiberStats['time'])) {
88+
$timeRatio = $fiberStats['time'] / $coroutineStats['time'];
89+
echo "⏱️ Execution Time:\n";
90+
echo " Coroutines: " . number_format($coroutineStats['time'], 4) . "s\n";
91+
echo " Fibers: " . number_format($fiberStats['time'], 4) . "s\n";
92+
if ($timeRatio > 1) {
93+
echo " 🏆 Coroutines are " . number_format($timeRatio, 2) . "x faster\n\n";
94+
} else {
95+
echo " 🏆 Fibers are " . number_format(1/$timeRatio, 2) . "x faster\n\n";
96+
}
97+
}
98+
99+
// Throughput comparison
100+
if (isset($coroutineStats['switches_per_sec']) && isset($fiberStats['switches_per_sec'])) {
101+
echo "🚀 Throughput (switches/sec):\n";
102+
echo " Coroutines: " . number_format($coroutineStats['switches_per_sec']) . "\n";
103+
echo " Fibers: " . number_format($fiberStats['switches_per_sec']) . "\n";
104+
$throughputRatio = $coroutineStats['switches_per_sec'] / $fiberStats['switches_per_sec'];
105+
if ($throughputRatio > 1) {
106+
echo " 🏆 Coroutines have " . number_format($throughputRatio, 2) . "x higher throughput\n\n";
107+
} else {
108+
echo " 🏆 Fibers have " . number_format(1/$throughputRatio, 2) . "x higher throughput\n\n";
109+
}
110+
}
111+
112+
// Overhead comparison
113+
if (isset($coroutineStats['overhead_us']) && isset($fiberStats['overhead_us'])) {
114+
echo "⚡ Overhead per switch:\n";
115+
echo " Coroutines: " . number_format($coroutineStats['overhead_us'], 2) . " μs\n";
116+
echo " Fibers: " . number_format($fiberStats['overhead_us'], 2) . " μs\n";
117+
$overheadRatio = $fiberStats['overhead_us'] / $coroutineStats['overhead_us'];
118+
if ($overheadRatio > 1) {
119+
echo " 🏆 Coroutines have " . number_format($overheadRatio, 2) . "x lower overhead\n\n";
120+
} else {
121+
echo " 🏆 Fibers have " . number_format(1/$overheadRatio, 2) . "x lower overhead\n\n";
122+
}
123+
}
124+
125+
// Memory comparison
126+
if (isset($coroutineStats['memory_mb']) && isset($fiberStats['memory_mb'])) {
127+
echo "💾 Memory Usage:\n";
128+
echo " Coroutines: " . number_format($coroutineStats['memory_mb'], 2) . " MB\n";
129+
echo " Fibers: " . number_format($fiberStats['memory_mb'], 2) . " MB\n";
130+
$memoryRatio = $fiberStats['memory_mb'] / $coroutineStats['memory_mb'];
131+
if ($memoryRatio > 1) {
132+
echo " 🏆 Coroutines use " . number_format($memoryRatio, 2) . "x less memory\n\n";
133+
} else {
134+
echo " 🏆 Fibers use " . number_format(1/$memoryRatio, 2) . "x less memory\n\n";
135+
}
136+
}
137+
} else {
138+
echo "⚠️ Cannot compare results - one or both benchmarks failed\n";
139+
}
140+
141+
echo "💡 Note: Results may vary based on system load and configuration\n";
142+
echo "💡 Run multiple times and average results for production comparisons\n";
143+
144+
echo "\nComparison completed.\n";
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
/**
3+
* Benchmark: Async Coroutines switching performance
4+
* Tests the performance of async coroutines context switching
5+
*/
6+
7+
// Increase memory limit for benchmark
8+
ini_set('memory_limit', '512M');
9+
10+
use function Async\spawn;
11+
use function Async\awaitAll;
12+
13+
echo "=== Async Coroutines Benchmark ===\n\n";
14+
15+
// Test configuration
16+
$iterations = 1000;
17+
$switches = 1000;
18+
19+
// Benchmark async coroutines
20+
function benchmarkCoroutines($iterations, $switches) {
21+
$start = microtime(true);
22+
$memoryBeforeCreate = getCurrentMemoryUsage();
23+
24+
$coroutines = [];
25+
for ($i = 0; $i < $iterations; $i++) {
26+
$coroutines[] = spawn(function() use ($switches) {
27+
for ($j = 0; $j < $switches; $j++) {
28+
// Yield control to other coroutines
29+
Async\suspend();
30+
}
31+
});
32+
}
33+
34+
$memoryAfterCreate = getCurrentMemoryUsage();
35+
36+
awaitAll($coroutines);
37+
38+
$end = microtime(true);
39+
return [
40+
'time' => $end - $start,
41+
'memoryBeforeCreate' => $memoryBeforeCreate,
42+
'memoryAfterCreate' => $memoryAfterCreate,
43+
'creationOverhead' => $memoryAfterCreate - $memoryBeforeCreate
44+
];
45+
}
46+
47+
// Memory usage tracking
48+
function getCurrentMemoryUsage() {
49+
return memory_get_usage(true);
50+
}
51+
52+
function getPeakMemoryUsage() {
53+
return memory_get_peak_usage(true);
54+
}
55+
56+
// Run benchmark
57+
echo "Configuration:\n";
58+
echo "- Iterations: $iterations\n";
59+
echo "- Switches per iteration: $switches\n";
60+
echo "- Total context switches: " . ($iterations * $switches) . "\n\n";
61+
62+
// Memory usage before benchmark
63+
$memoryBefore = getCurrentMemoryUsage();
64+
65+
// Warmup
66+
echo "Warming up...\n";
67+
benchmarkCoroutines(100, 10);
68+
69+
echo "\nRunning coroutines benchmark...\n";
70+
71+
// Benchmark coroutines
72+
$result = benchmarkCoroutines($iterations, $switches);
73+
$coroutineTime = $result['time'];
74+
75+
// Memory usage after benchmark
76+
$memoryAfter = getCurrentMemoryUsage();
77+
$memoryPeak = getPeakMemoryUsage();
78+
79+
// Results
80+
echo "\n=== Results ===\n";
81+
echo "Time: " . number_format($coroutineTime, 4) . " seconds\n";
82+
echo "Switches per second: " . number_format(($iterations * $switches) / $coroutineTime, 0) . "\n";
83+
echo "Overhead per switch: " . number_format(($coroutineTime / ($iterations * $switches)) * 1000000, 2) . " μs\n";
84+
85+
echo "\nMemory Usage:\n";
86+
echo "Before: " . number_format($memoryBefore / 1024 / 1024, 2) . " MB\n";
87+
echo "After creation: " . number_format($result['memoryAfterCreate'] / 1024 / 1024, 2) . " MB\n";
88+
echo "After completion: " . number_format($memoryAfter / 1024 / 1024, 2) . " MB\n";
89+
echo "Peak: " . number_format($memoryPeak / 1024 / 1024, 2) . " MB\n";
90+
echo "Creation overhead: " . number_format($result['creationOverhead'] / 1024 / 1024, 2) . " MB\n";
91+
echo "Used for benchmark: " . number_format(($memoryAfter - $memoryBefore) / 1024 / 1024, 2) . " MB\n";
92+
93+
// Additional metrics
94+
$totalSwitches = $iterations * $switches;
95+
echo "\nPerformance Metrics:\n";
96+
echo "Total coroutines created: $iterations\n";
97+
echo "Total context switches: $totalSwitches\n";
98+
echo "Average time per coroutine: " . number_format($coroutineTime / $iterations * 1000, 2) . " ms\n";
99+
echo "Memory per coroutine (creation): " . number_format($result['creationOverhead'] / $iterations, 0) . " bytes\n";
100+
echo "Memory per coroutine (total): " . number_format(($memoryAfter - $memoryBefore) / $iterations, 0) . " bytes\n";
101+
102+
echo "\nCoroutines benchmark completed.\n";

0 commit comments

Comments
 (0)