
@xf3l1x
f3l1x.io
12.05.2023
ย
FAST & FURIOUS
SPEED

SPEED

RESEARCH
SPEED

RESEARCH
PHP is used by 77.5%ย of all the websites whose server-side programming language we know.




PHP lifecycle



Research goals
- PHP 8.2
- Docker
- Reproducible
- Extendable
- Collect experience
- Find winner
Divide & conquer
Coroutines
Eventloops
Extensions
Process managers
Built-in
Divide & conquer
Webservers
Frameworks
Process managers
Extensions
Webservers
- apache
- nginx
- caddy
- unit
Webservers
Apache

Webservers
Apache
- well established
- modules
- htaccess ๐
- hard configuration ๐
<VirtualHost *:8000>
DocumentRoot /srv
<FilesMatch \.php$>
SetHandler "proxy:unix:/var/run/php-fpm.sock|fcgi://localhost/"
</FilesMatch>
<Directory /srv>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>Webservers
Nginx
- well established
- modules
- missing htaccess ๐
- easy configuration ๐
server {
listen 8000;
index index.php;
root /srv/;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~* \.php$ {
fastcgi_pass app;
fastcgi_index index.php;
include fastcgi_params;
try_files $uri =404;
}
}Webservers
Caddy
- not so established ๐
- modules
- easy configuration ๐๐๐
# Listening on
:8000
# Set the document root of the site.
root * /srv
# Set the path to the php-fpm process.
php_fastcgi unix//var/run/php-fpm.sock
# Global options
{
# Disable admin
admin off
}Webservers
Unit
- not so established ๐
- lightweight
- support PHP, Java, Go, Python, ...
- easy configuration ๐๐๐
{
"listeners": {
"*:8000": { "pass": "routes" }
},
"routes": [
{
"action": {
"share": "/srv/www$uri",
"fallback": { "pass": "applications/app" }
}
}
],
"applications": {
"app": {
"type": "php",
"root": "/srv/",
"script": "index.php",
"options": {
"admin": {
"memory_limit": "256M",
}
}
}
}
}Frameworks
- amphp
- reactphp
- workerman
- php server
Frameworks
Amphp
Loop::run(function () {
$sockets = [Socket\listen("0.0.0.0:8000")];
$server = new Server(
$sockets,
new CallableRequestHandler(static function (Request $request) {
echo "Request received {$request->getUri()}" . PHP_EOL;
return new Response(
Status::OK,
['Content-type' => 'application/json'],
json_encode([
'php' => PHP_VERSION,
'webserver' => 'Amphp',
]));
}
), new NullLogger);
echo "Listening on 0.0.0.0:8000" . PHP_EOL;
yield $server->start();
});- event loop (revolt)
- many packages
- great for CLI ๐
- knowledge ๐งโโ๏ธ
Frameworks
Reactphp
- event loop
- well established
- many packages
- framework X ๐
- knowledge ๐งโโ๏ธ
$http = new HttpServer(function ($request) {
echo "Request received {$request->getUri()}" . PHP_EOL;
return Response::json([
'php' => PHP_VERSION,
'webserver' => 'ReactPHP',
]);
});
$socket = new SocketServer('0.0.0.0:8000');
$http->listen($socket);
echo "Listening on {$socket->getAddress()}" . PHP_EOL;Frameworks
Workerman
- event loop
- fast as f*uck ๐
- hard to start ๐
- knowledge ๐งโโ๏ธ
$worker = new Worker('http://0.0.0.0:8000');
$worker->count = 4;
$worker->onMessage = function ($connection, $request) {
echo "Request received {$request->uri()}" . PHP_EOL;
$connection->send(
new Response(
200,
['Content-type' => 'application/json'],
json_encode([
'php' => PHP_VERSION,
'webserver' => 'Workerman',
])
)
);
};
echo "Listening on {$worker->getSocketName()}" . PHP_EOL;
Worker::runAll();
Frameworks
PHP server
- native ๐
- local development
- really slow ๐
- no production โ
- knowledge ๐ถ
php -S 0.0.0.0:8000 /srv/index.php
PHP_CLI_SERVER_WORKERS=4 php -S 0.0.0.0:8000 /srv/index.phpProcess managers
- php-pm
- roadrunner
Process managers
PHP-PM
{
"host": "0.0.0.0",
"port": 8000,
"workers": 8,
"static-directory": "www",
"logging": 0,
"max-requests": 2000,
"bridge": "AppBridge",
"bootstrap": "AppBridge"
}- low maintenance ๐
- hard to start ๐
- memory leaks ๐
- knowledge ๐งโโ๏ธ
class AppBridge implements BridgeInterface
{
public function handle($request): ResponseInterface
{
return new Response(
200,
['Content-type' => 'application/json'],
json_encode([
'php' => PHP_VERSION,
'webserver' => 'PHP-PM',
])
);
}
}Process managers
Roadrunner
- Go
- many packages
- easy to start ๐
- fast โฉ
- knowledge ๐งโโ๏ธ
$worker = Worker::create();
$psrFactory = new Psr17Factory();
$worker = new PSR7Worker($worker, $psrFactory, $psrFactory, $psrFactory);
while ($req = $worker->waitRequest()) {
$rsp = new Response( 200, ['Content-type' => 'application/json'],
json_encode([
'php' => PHP_VERSION,
'webserver' => 'Roadrunner',
])
);
$worker->respond($rsp);
}version: '3'
rpc:
listen: tcp://127.0.0.1:6001
server:
command: "php /srv/index.php"
http:
address: "0.0.0.0:8000"
Extensions
- swoole
- openswoole
- swow
Extensions
Swoole
$http = new Server('0.0.0.0', 8000);
$http->set(['hook_flags' => SWOOLE_HOOK_ALL]);
$http->on('request', function ($request, $response) {
echo "Request received {$request->server['request_uri']}" . PHP_EOL;
$response->setHeader('content-type', 'application/json');
$response->end(json_encode([
'php' => PHP_VERSION,
'webserver' => 'Swoole',
]));
});
echo "Swoole http server is started at http://0.0.0.0:8000" . PHP_EOL;
$http->start();- extension ๐
- poor docs
- black magic ๐
- fast โฉ
- knowledge ๐งโโ๏ธ๐ฆ๐ฆ
Extensions
Openswoole
- extension ๐
- better docs
- black magic ๐
- fast โฉ
- knowledge ๐งโโ๏ธ๐ฆ๐ฆ
$server = new Server("0.0.0.0", 8000);
$server->on("Start", function (Server $server) {
echo "OpenSwoole http server is started at http://0.0.0.0:8000\n";
});
$server->on("Request", function ($request, $response) {
echo "Request received {$request->server['request_uri']}" . PHP_EOL;
$response->header("Content-Type", "application/json");
$response->end(json_encode([
'php' => PHP_VERSION,
'webserver' => 'OpenSwoole',
]));
});
$server->start();Extensions
Swow
- extension ๐๐
- no docs
- black magic ๐๐๐
- fast โฉ
- knowledge ๐งโโ๏ธ๐ฆ๐ฆ
$server = new Server();
$server->bind('0.0.0.0', 8000);
$server->listen();
echo "Swow http server is started at http://0.0.0.0:8000\n";
Coroutine::run(function () use ($server) {
while (true) {
try {
$connection = $server->acceptConnection();
Coroutine::run(function () use ($connection) {
try {
while (true) {
$request = null;
try {
$request = $connection->recvHttpRequest();
echo "Request received {$request->getUri()}" . PHP_EOL;
$response = new Response();
$response->setStatus(200);
$response->setHeader('Content-type', 'application/json');
$response->getBody()->write(json_encode([
'php' => PHP_VERSION,
'webserver' => 'Swow',
]));
$connection->sendHttpResponse($response);
} catch (ProtocolException $exception) {
$connection->error($exception->getCode(), $exception->getMessage());
}
if (!$request || !Psr7::detectShouldKeepAlive($request)) {
break;
}
}
} catch (Throwable $exception) {
echo $exception->getMessage() . PHP_EOL;
} finally {
$connection->close();
}
});
} catch (SocketException|CoroutineException $exception) {
if (in_array($exception->getCode(), [Errno::EMFILE, Errno::ENFILE, Errno::ENOMEM], true)) {
echo "Socket resources have been exhausted." . PHP_EOL;
sleep(1);
} else {
echo $exception->getMessage() . PHP_EOL;
break;
}
} catch (Throwable $exception) {
echo $exception->getMessage() . PHP_EOL;
}
}
});
waitAll();Benchmark tools
ab
ab -k -c 1 -n 10000 http://localhost:8000wrk
wrk -t10 -c10 -d30s http://localhost:8000hey
hey -n 10000 -c 100 -z 1s http://localhost:8000plow
plow -c 1000 -n 10000 http://localhost:8000Benchmark tools
ab
ab -k -c 1 -n 10000 http://localhost:8000wrk
wrk -t10 -c10 -d30s http://localhost:8000hey
hey -n 10000 -c 100 -z 1s http://localhost:8000plow
plow -c 1000 -n 10000 http://localhost:8000
Benchmark tools
{
"Percentiles": {
"P50": "944ยตs",
"P75": "1.007ms",
"P90": "1.064ms",
"P95": "1.102ms",
"P99": "4.413ms",
"P99.9": "494.124ms",
"P99.99": "494.789ms"
},
"Histograms": [
[ "932ยตs", 8050 ],
[ "1.132ms", 1820 ],
[ "3.511ms", 84 ],
[ "4.559ms", 34 ],
[ "8.31ms", 2 ],
[ "494.274ms", 4 ],
[ "494.479ms", 3 ],
[ "494.672ms", 3 ]
]
}{
"Summary": {
"Elapsed": "1.5s",
"Count": 10000,
"Counts": {
"2xx": 10000
},
"RPS": 6661.748,
"Reads": "1.906MB/s",
"Writes": "0.368MB/s"
}
}plow -c10 -n10000 --summary --json http://localhost:8000
Results?
Benchmarks
- concurrency 1 / requests 10000
- concurrency 10 / requests 10000
- concurrency 100 / requests 10000
- concurrency 1000 / requests 10000
Apple M1 Ultra / 128 GB| App | Type | C1/R10000 | C10/R10000 | C100/R10000 | C1000/R10000 |
|---|---|---|---|---|---|
| apache+fpm | webserver | 1423 | 8139 | 14705 | 2937 |
| nginx+fpm | webserver | 1642 | 9215 | 20944 | 15006 |
| caddy+fpm | webserver | 953 | 7941 | 17318 | 15618 |
| unit | webserver | 1968 | 10164 | 19593 | 19294 |
| php-server | PHP built-in | 376 | 438 | 428 | 800 |
| amphp | eventloop | 2213 | 10164 | 19593 | 19249 |
| reactphp | eventloop | 2056 | 10190 | 16403 | 15919 |
| workerman | eventloop | 2389 | 11873 | 31402 | 14314 |
| php-pm | PM | 445 | 790 | 454 | 655 |
| roadrunner | PM [go] | 1686 | 9437 | 29230 | 19733 |
| openswoole | extension | 1711 | 10303 | 32694 | 20084 |
| swoole | extension | 2193 | 11572 | 25597 | 18626 |
| swow | extension | 2011 | 11053 | 23187 | 17426 |
*on my computerConclusion
-
PHP is great
-
ecosystem is great
-
serverless PHP (bref/vercel)
-
nativephp (self-executable) [WIP]
-
phpstan, codesniffer, phpunit
-
symfony, laravel, nette

-
PHP dev server [local] -
Nginx + FPM [classic] -
Nginx Unit [micros] -
Roadrunner [win+win] -
Openswoole [speed]


Let's connect
github.com/contributte/benchmarks
Time to selfie ๐ท

Thank you!


@xf3l1x
f3l1x.io
