@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.php

Process 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:8000
wrk
wrk -t10 -c10 -d30s http://localhost:8000
hey
hey -n 10000 -c 100 -z 1s http://localhost:8000
plow
plow -c 1000 -n 10000 http://localhost:8000

Benchmark tools

ab
ab -k -c 1 -n 10000 http://localhost:8000
wrk
wrk -t10 -c10 -d30s http://localhost:8000
hey
hey -n 10000 -c 100 -z 1s http://localhost:8000
plow
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 computer

Conclusion

  • 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

2023-05-12 - Fast & Furious: PHP

By Milan Felix ล ulc

2023-05-12 - Fast & Furious: PHP

  • 531