Skip to content

Commit 182bdda

Browse files
committed
Add benchmarks
1 parent 38bd898 commit 182bdda

10 files changed

Lines changed: 288 additions & 15 deletions

.gitignore

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ run-tests.php
4141
.idea/
4242
.vscode/
4343

44-
# tmp/ is tracked (benchmarks), but ignore vendor inside it
45-
tmp/vendor/
46-
tmp/composer.lock
44+
# Benchmark dependencies
45+
benchmark/vendor/
46+
benchmark/composer.lock
47+
48+
# Legacy tmp
49+
tmp/

README.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -191,18 +191,20 @@ echo EventLoop::getDriver(); // "epoll" on Linux, "kqueue" on macOS
191191

192192
## Benchmarks
193193

194-
**Environment:** PHP 8.5.4, Apple M1 Max, macOS, 100,000 iterations.
195-
Revolt v1.0.8 with StreamSelectDriver (default, no ext-ev/ext-uv). ext-eventloop using kqueue driver.
196-
197-
| Benchmark | Revolt | ext-eventloop | Speedup |
198-
|---|--:|--:|--:|
199-
| `defer()` dispatch | 715,053 ops/sec | 3,712,263 ops/sec | **5.2x** |
200-
| `delay(0)` dispatch | 234,229 ops/sec | 2,832,500 ops/sec | **12.1x** |
201-
| `repeat()` dispatch | 679,452 ops/sec | 17,794,516 ops/sec | **26.2x** |
202-
| I/O register + cancel | 2,109,267 ops/sec | 7,635,677 ops/sec | **3.6x** |
203-
| Fiber suspend/resume | 221,776 ops/sec | 248,738 ops/sec | **1.1x** |
204-
205-
> **Note:** Revolt was tested with its default StreamSelectDriver. With ext-ev or ext-uv backends, Revolt's I/O performance would be higher, though callback dispatch overhead remains in PHP userland. Fiber performance is nearly identical because `suspend()`/`resume()` is handled by the Zend Engine in both cases.
194+
**Environment:** PHP 8.5.4, Apple M1 Max, macOS, 100,000 iterations (average of 3 runs).
195+
Revolt v1.0.8 tested with all four available drivers ([StreamSelect, Ev, Event, UV](https://revolt.run/extensions)). ext-eventloop using kqueue driver.
196+
197+
| Benchmark | Revolt StreamSelect | Revolt Ev | Revolt Event | Revolt UV | ext-eventloop |
198+
|---|--:|--:|--:|--:|--:|
199+
| `defer()` | 755,979 ops/sec | 726,468 ops/sec | 741,084 ops/sec | 730,488 ops/sec | **3,696,132 ops/sec** |
200+
| `delay(0)` | 245,389 ops/sec | 480,866 ops/sec | 467,362 ops/sec | 185,068 ops/sec | **3,041,443 ops/sec** |
201+
| `repeat(0)` | 694,763 ops/sec | 712,077 ops/sec | 74,929 ops/sec | 73,899 ops/sec | **17,971,855 ops/sec** |
202+
| I/O register + cancel | 2,149,497 ops/sec | 2,056,740 ops/sec | 2,034,001 ops/sec | 2,000,214 ops/sec | **7,642,510 ops/sec** |
203+
| Fiber suspend/resume | 219,349 ops/sec | 217,095 ops/sec | 220,924 ops/sec | 215,305 ops/sec | **242,691 ops/sec** |
204+
205+
> These benchmarks measure callback dispatch and scheduling throughput. The I/O backend (StreamSelect, Ev, Event, UV) primarily affects polling efficiency at high concurrency, not dispatch speed — that's why all four Revolt drivers show similar numbers here. ext-eventloop moves the entire dispatch path into C, which is where the difference comes from. Fiber performance is nearly identical because `suspend()`/`resume()` is handled by the Zend Engine directly.
206+
>
207+
> **[Run the benchmarks yourself →](benchmark/)**
206208
207209
## Migrating from Revolt
208210

benchmark/README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Benchmarks
2+
3+
Callback dispatch throughput benchmarks comparing ext-eventloop with Revolt across all available drivers.
4+
5+
## Setup
6+
7+
```bash
8+
cd benchmark
9+
composer install
10+
```
11+
12+
For Revolt driver comparison, install the desired extensions:
13+
14+
```bash
15+
pecl install ev # EvDriver
16+
pecl install event # EventDriver (requires libevent)
17+
pecl install uv-beta # UvDriver (requires libuv)
18+
```
19+
20+
## Running
21+
22+
```bash
23+
# ext-eventloop (requires ext-eventloop installed)
24+
php bench_eventloop.php [iterations]
25+
26+
# Revolt drivers
27+
php bench_revolt_stream_select.php [iterations]
28+
php bench_revolt_ev.php [iterations]
29+
php bench_revolt_event.php [iterations]
30+
php bench_revolt_uv.php [iterations]
31+
```
32+
33+
Default: 100,000 iterations.
34+
35+
## What is measured
36+
37+
| Benchmark | What it tests |
38+
|---|---|
39+
| `defer()` | Queue and dispatch deferred callbacks |
40+
| `delay(0)` | Schedule and fire zero-delay timers |
41+
| `repeat(0)` | Repeated timer dispatch throughput |
42+
| I/O register + cancel | onReadable registration and cancellation overhead |
43+
| Fiber suspend/resume | Fiber context switching via Suspension |
44+
45+
These benchmarks measure **callback dispatch overhead** — how fast the loop can register, schedule, and invoke callbacks. The I/O backend primarily affects polling efficiency at high concurrency, not dispatch speed.
46+
47+
## File structure
48+
49+
| File | Description |
50+
|---|---|
51+
| `bench.php` | Shared benchmark logic |
52+
| `bench_eventloop.php` | ext-eventloop entry point |
53+
| `bench_revolt_stream_select.php` | Revolt StreamSelectDriver |
54+
| `bench_revolt_ev.php` | Revolt EvDriver (pecl/ev) |
55+
| `bench_revolt_event.php` | Revolt EventDriver (pecl/event) |
56+
| `bench_revolt_uv.php` | Revolt UvDriver (pecl/uv) |

benchmark/bench.php

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use EventLoop\EventLoop as PhpLoop;
6+
use Revolt\EventLoop as RevoltLoop;
7+
8+
/**
9+
* @var class-string<PhpLoop|RevoltLoop> $loop
10+
*/
11+
12+
$iterations = (int) ($argv[1] ?? 100000);
13+
14+
printf("Iterations: %d\n\n", $iterations);
15+
16+
/**
17+
* 1. defer() dispatch throughput
18+
*/
19+
$count = 0;
20+
$start = hrtime(true);
21+
22+
for ($i = 0; $i < $iterations; $i++) {
23+
$loop::defer(function () use (&$count) {
24+
$count++;
25+
});
26+
}
27+
$loop::run();
28+
29+
$elapsed = (hrtime(true) - $start) / 1e9;
30+
printf("defer(): %d callbacks in %.4fs (%.0f ops/sec)\n", $count, $elapsed, $count / $elapsed);
31+
32+
/**
33+
* 2. delay(0) dispatch throughput
34+
*/
35+
$count = 0;
36+
$start = hrtime(true);
37+
38+
for ($i = 0; $i < $iterations; $i++) {
39+
$loop::delay(0, function () use (&$count) {
40+
$count++;
41+
});
42+
}
43+
$loop::run();
44+
45+
$elapsed = (hrtime(true) - $start) / 1e9;
46+
printf("delay(0): %d callbacks in %.4fs (%.0f ops/sec)\n", $count, $elapsed, $count / $elapsed);
47+
48+
/**
49+
* 3. repeat(0) dispatch throughput
50+
*/
51+
$count = 0;
52+
$target = $iterations;
53+
$start = hrtime(true);
54+
55+
$loop::repeat(0, function (string $cbId) use (&$count, $target, $loop) {
56+
$count++;
57+
if ($count >= $target) {
58+
$loop::cancel($cbId);
59+
}
60+
});
61+
$loop::run();
62+
63+
$elapsed = (hrtime(true) - $start) / 1e9;
64+
printf("repeat(0): %d callbacks in %.4fs (%.0f ops/sec)\n", $count, $elapsed, $count / $elapsed);
65+
66+
/**
67+
* 4. I/O callback registration + cancel overhead
68+
*/
69+
$pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
70+
stream_set_blocking($pair[0], false);
71+
72+
$start = hrtime(true);
73+
74+
for ($i = 0; $i < $iterations; $i++) {
75+
$cbId = $loop::onReadable($pair[0], function () {
76+
});
77+
78+
$loop::cancel($cbId);
79+
}
80+
81+
$elapsed = (hrtime(true) - $start) / 1e9;
82+
printf("I/O reg+cancel: %d ops in %.4fs (%.0f ops/sec)\n", $iterations, $elapsed, $iterations / $elapsed);
83+
84+
fclose($pair[0]);
85+
fclose($pair[1]);
86+
87+
/**
88+
* 5. Fiber suspension/resume
89+
*/
90+
$count = 0;
91+
$suspensionIterations = min($iterations, 50000);
92+
$start = hrtime(true);
93+
94+
for ($i = 0; $i < $suspensionIterations; $i++) {
95+
$fiber = new Fiber(function () use (&$count, $loop) {
96+
$suspension = $loop::getSuspension();
97+
$suspension->suspend();
98+
$count++;
99+
});
100+
101+
$fiber->start();
102+
$fiber->resume();
103+
}
104+
105+
$elapsed = (hrtime(true) - $start) / 1e9;
106+
printf("fiber suspend/res: %d ops in %.4fs (%.0f ops/sec)\n", $count, $elapsed, $count / $elapsed);

benchmark/bench_eventloop.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use EventLoop\EventLoop;
6+
7+
ini_set('memory_limit', '512M');
8+
9+
$loop = EventLoop::class;
10+
11+
printf("Driver: %s\n", EventLoop::getDriver());
12+
13+
require __DIR__ . '/bench.php';

benchmark/bench_revolt_ev.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Benchmark: Revolt with EvDriver (requires ev)
7+
*
8+
* Usage: php bench_revolt_ev.php [iterations]
9+
*/
10+
error_reporting(E_ALL & ~E_DEPRECATED);
11+
12+
ini_set('memory_limit', '512M');
13+
14+
putenv('REVOLT_DRIVER=Revolt\EventLoop\Driver\EvDriver');
15+
16+
require __DIR__ . '/vendor/autoload.php';
17+
18+
$loop = \Revolt\EventLoop::class;
19+
20+
printf("Driver: %s\n", get_class(\Revolt\EventLoop::getDriver()));
21+
22+
require __DIR__ . '/bench.php';

benchmark/bench_revolt_event.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Benchmark: Revolt with EventDriver (requires event)
7+
*
8+
* Usage: php bench_revolt_event.php [iterations]
9+
*/
10+
error_reporting(E_ALL & ~E_DEPRECATED);
11+
12+
ini_set('memory_limit', '512M');
13+
14+
putenv('REVOLT_DRIVER=Revolt\EventLoop\Driver\EventDriver');
15+
16+
require __DIR__ . '/vendor/autoload.php';
17+
18+
$loop = \Revolt\EventLoop::class;
19+
20+
printf("Driver: %s\n", get_class(\Revolt\EventLoop::getDriver()));
21+
22+
require __DIR__ . '/bench.php';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Benchmark: Revolt with StreamSelectDriver (default, no extensions required)
7+
*
8+
* Usage: php bench_revolt_stream_select.php [iterations]
9+
*/
10+
error_reporting(E_ALL & ~E_DEPRECATED);
11+
12+
ini_set('memory_limit', '512M');
13+
14+
putenv('REVOLT_DRIVER=Revolt\EventLoop\Driver\StreamSelectDriver');
15+
16+
require __DIR__ . '/vendor/autoload.php';
17+
18+
$loop = \Revolt\EventLoop::class;
19+
20+
printf("Driver: %s\n", get_class(\Revolt\EventLoop::getDriver()));
21+
22+
require __DIR__ . '/bench.php';

benchmark/bench_revolt_uv.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Benchmark: Revolt with UvDriver (requires uv)
7+
*
8+
* Usage: php bench_revolt_uv.php [iterations]
9+
*/
10+
error_reporting(E_ALL & ~E_DEPRECATED);
11+
12+
ini_set('memory_limit', '512M');
13+
14+
putenv('REVOLT_DRIVER=Revolt\EventLoop\Driver\UvDriver');
15+
16+
require __DIR__ . '/vendor/autoload.php';
17+
18+
$loop = \Revolt\EventLoop::class;
19+
20+
printf("Driver: %s\n", get_class(\Revolt\EventLoop::getDriver()));
21+
22+
require __DIR__ . '/bench.php';

benchmark/composer.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"require": {
3+
"revolt/event-loop": "^1.0"
4+
}
5+
}

0 commit comments

Comments
 (0)