├── .gitignore ├── LICENSE.md ├── README.md ├── assets ├── CreatorFactory.php ├── GuzzleFactory.php ├── file.txt └── packages.php ├── benchmark.php └── composer.json /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Evgeniy Zyubin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Benchmark of PSR-7 and PSR-17 implementation 2 | 3 | This test performance for [PSR-7](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md) and [PSR-17](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-17-http-factory.md) implementations. We just create Request, ServerRequest, Reponse, Stream, Uri, UploadFile and corresponding factories objects and measure the time. Note that the number does not really matter. It is the difference between them that are interesting (higher is better). 4 | 5 | > Note: For more honest test results, disable the PHP CLI debugger and profiler tools before running the benchmark. 6 | 7 | ## Usage 8 | 9 | Install: 10 | 11 | ``` 12 | git clone git@github.com:devanych/psr-http-benchmark.git 13 | cd psr-http-benchmark 14 | composer update 15 | ``` 16 | 17 | Runs: 18 | 19 | ``` 20 | php benchmark.php [runs] 21 | 22 | # The default number of runs is 30000. 23 | php benchmark.php httpsoft 30000 24 | # equivalently to: 25 | php benchmark.php httpsoft 26 | 27 | # guzzlehttp/psr7: 28 | php benchmark.php guzzle 29 | 30 | # httpsoft/http-message: 31 | php benchmark.php httpsoft 32 | 33 | # laminas/laminas-diactoros: 34 | php benchmark.php laminas 35 | 36 | # nyholm/psr7: 37 | php benchmark.php nyholm 38 | 39 | # slim/psr7: 40 | php benchmark.php slim 41 | ``` 42 | 43 | Run and result: 44 | 45 | ``` 46 | php benchmark.php httpsoft 47 | 48 | Runs: 30,000 49 | Runs per second: 19544 50 | Average time per run: 0.0512 ms 51 | Total time: 1.5349 s 52 | ``` 53 | 54 | ## Test results 55 | 56 | | Runs: 30,000 | Guzzle | HttpSoft | Laminas | Nyholm | Slim | 57 | |----------------------|-----------|-----------|-----------|-----------|-----------| 58 | | Runs per second | 15868 | 19544 | 12257 | 19022 | 12117 | 59 | | Average time per run | 0.0630 ms | 0.0512 ms | 0.0816 ms | 0.0526 ms | 0.0825 ms | 60 | | Total time | 1.8905 s | 1.5349 s | 2.4474 s | 1.5771 s | 2.4757 s | 61 | 62 | --- 63 | 64 | | Runs: 50,000 | Guzzle | HttpSoft | Laminas | Nyholm | Slim | 65 | |----------------------|-----------|-----------|-----------|-----------|-----------| 66 | | Runs per second | 15698 | 19438 | 12521 | 19003 | 12046 | 67 | | Average time per run | 0.0637 ms | 0.0514 ms | 0.0799 ms | 0.0526 ms | 0.0830 ms | 68 | | Total time | 3.1849 s | 2.5723 s | 3.9932 s | 2.6311 s | 4.1504 s | 69 | 70 | --- 71 | 72 | | Runs: 100,000 | Guzzle | HttpSoft | Laminas | Nyholm | Slim | 73 | |----------------------|-----------|-----------|-----------|-----------|-----------| 74 | | Runs per second | 15471 | 19436 | 12447 | 19121 | 12041 | 75 | | Average time per run | 0.0647 ms | 0.0515 ms | 0.0803 ms | 0.0523 ms | 0.0830 ms | 76 | | Total time | 6.4685 s | 5.1450 s | 8.0338 s | 5.2298 s | 8.3044 s | 77 | -------------------------------------------------------------------------------- /assets/CreatorFactory.php: -------------------------------------------------------------------------------- 1 | createRequest($this->method, $this->uri); 14 | } 15 | 16 | public function createServerRequest(Psr\Http\Message\ServerRequestFactoryInterface $factory): void 17 | { 18 | $factory->createServerRequest($this->method, $this->uri, [ 19 | 'HTTPS' => 'on', 20 | 'HTTP_HOST' => 'example.com', 21 | 'SERVER_PROTOCOL' => 'HTTP/1.1', 22 | 'REQUEST_METHOD' => 'GET', 23 | ]); 24 | } 25 | 26 | public function createResponse(Psr\Http\Message\ResponseFactoryInterface $factory): void 27 | { 28 | $factory->createResponse(200, 'OK'); 29 | } 30 | 31 | public function createUri(Psr\Http\Message\UriFactoryInterface $factory): void 32 | { 33 | $factory->createUri($this->uri); 34 | } 35 | 36 | public function createStream(Psr\Http\Message\StreamFactoryInterface $factory): void 37 | { 38 | $stream = $factory->createStream('content'); 39 | $stream->rewind(); 40 | $stream->read(3); 41 | $stream = $factory->createStreamFromFile($this->file); 42 | $stream->rewind(); 43 | $stream->read(3); 44 | $stream = $factory->createStreamFromResource(fopen('php://temp', 'wb+')); 45 | $stream->rewind(); 46 | $stream->read(3); 47 | } 48 | 49 | public function createUploadedFile( 50 | Psr\Http\Message\UploadedFileFactoryInterface $factory, 51 | Psr\Http\Message\StreamFactoryInterface $streamFactory 52 | ): void { 53 | $stream = $streamFactory->createStreamFromFile($this->file); 54 | $factory->createUploadedFile($stream, $stream->getSize(), UPLOAD_ERR_OK, 'file.txt', 'text/plain'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /assets/GuzzleFactory.php: -------------------------------------------------------------------------------- 1 | getSize(); 39 | } 40 | 41 | return new GuzzleHttp\Psr7\UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType); 42 | } 43 | 44 | public function createStream(string $content = ''): StreamInterface 45 | { 46 | return GuzzleHttp\Psr7\Utils::streamFor($content); 47 | } 48 | 49 | public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface 50 | { 51 | try { 52 | $resource = GuzzleHttp\Psr7\Utils::tryFopen($filename, $mode); 53 | } catch (\RuntimeException $e) { 54 | if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) { 55 | throw new \InvalidArgumentException(sprintf('Invalid file opening mode "%s"', $mode), 0, $e); 56 | } 57 | 58 | throw $e; 59 | } 60 | 61 | return GuzzleHttp\Psr7\Utils::streamFor($resource); 62 | } 63 | 64 | public function createStreamFromResource($resource): StreamInterface 65 | { 66 | return GuzzleHttp\Psr7\Utils::streamFor($resource); 67 | } 68 | 69 | public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface 70 | { 71 | if (empty($method)) { 72 | if (!empty($serverParams['REQUEST_METHOD'])) { 73 | $method = $serverParams['REQUEST_METHOD']; 74 | } else { 75 | throw new \InvalidArgumentException('Cannot determine HTTP method'); 76 | } 77 | } 78 | 79 | return new GuzzleHttp\Psr7\ServerRequest($method, $uri, [], null, '1.1', $serverParams); 80 | } 81 | 82 | public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface 83 | { 84 | return new GuzzleHttp\Psr7\Response($code, [], null, '1.1', $reasonPhrase); 85 | } 86 | 87 | public function createRequest(string $method, $uri): RequestInterface 88 | { 89 | return new GuzzleHttp\Psr7\Request($method, $uri); 90 | } 91 | 92 | public function createUri(string $uri = ''): UriInterface 93 | { 94 | return new GuzzleHttp\Psr7\Uri($uri); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /assets/file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devanych/psr-http-benchmark/22c8e5a2e240d36afaf6971a00cb2c90b09d3815/assets/file.txt -------------------------------------------------------------------------------- /assets/packages.php: -------------------------------------------------------------------------------- 1 | [ 16 | RequestFactoryInterface::class => GuzzleFactory::class, 17 | ResponseFactoryInterface::class => GuzzleFactory::class, 18 | ServerRequestFactoryInterface::class => GuzzleFactory::class, 19 | StreamFactoryInterface::class => GuzzleFactory::class, 20 | UploadedFileFactoryInterface::class => GuzzleFactory::class, 21 | UriFactoryInterface::class => GuzzleFactory::class, 22 | ], 23 | 'httpsoft' => [ 24 | RequestFactoryInterface::class => HttpSoft\Message\RequestFactory::class, 25 | ResponseFactoryInterface::class => HttpSoft\Message\ResponseFactory::class, 26 | ServerRequestFactoryInterface::class => HttpSoft\Message\ServerRequestFactory::class, 27 | StreamFactoryInterface::class => HttpSoft\Message\StreamFactory::class, 28 | UploadedFileFactoryInterface::class => HttpSoft\Message\UploadedFileFactory::class, 29 | UriFactoryInterface::class => HttpSoft\Message\UriFactory::class, 30 | ], 31 | 'laminas' => [ 32 | RequestFactoryInterface::class => Laminas\Diactoros\RequestFactory::class, 33 | ResponseFactoryInterface::class => Laminas\Diactoros\ResponseFactory::class, 34 | ServerRequestFactoryInterface::class => Laminas\Diactoros\ServerRequestFactory::class, 35 | StreamFactoryInterface::class => Laminas\Diactoros\StreamFactory::class, 36 | UploadedFileFactoryInterface::class => Laminas\Diactoros\UploadedFileFactory::class, 37 | UriFactoryInterface::class => Laminas\Diactoros\UriFactory::class, 38 | ], 39 | 'nyholm' => [ 40 | RequestFactoryInterface::class => Nyholm\Psr7\Factory\Psr17Factory::class, 41 | ResponseFactoryInterface::class => Nyholm\Psr7\Factory\Psr17Factory::class, 42 | ServerRequestFactoryInterface::class => Nyholm\Psr7\Factory\Psr17Factory::class, 43 | StreamFactoryInterface::class => Nyholm\Psr7\Factory\Psr17Factory::class, 44 | UploadedFileFactoryInterface::class => Nyholm\Psr7\Factory\Psr17Factory::class, 45 | UriFactoryInterface::class => Nyholm\Psr7\Factory\Psr17Factory::class, 46 | ], 47 | 'slim' => [ 48 | RequestFactoryInterface::class => Slim\Psr7\Factory\RequestFactory::class, 49 | ResponseFactoryInterface::class => Slim\Psr7\Factory\ResponseFactory::class, 50 | ServerRequestFactoryInterface::class => Slim\Psr7\Factory\ServerRequestFactory::class, 51 | StreamFactoryInterface::class => Slim\Psr7\Factory\StreamFactory::class, 52 | UploadedFileFactoryInterface::class => Slim\Psr7\Factory\UploadedFileFactory::class, 53 | UriFactoryInterface::class => Slim\Psr7\Factory\UriFactory::class, 54 | ], 55 | 'fatfree' => [ 56 | RequestFactoryInterface::class => F3\Http\Factory\Psr17Factory::class, 57 | ResponseFactoryInterface::class => F3\Http\Factory\Psr17Factory::class, 58 | ServerRequestFactoryInterface::class => F3\Http\Factory\Psr17Factory::class, 59 | StreamFactoryInterface::class => F3\Http\Factory\Psr17Factory::class, 60 | UploadedFileFactoryInterface::class => F3\Http\Factory\Psr17Factory::class, 61 | UriFactoryInterface::class => F3\Http\Factory\Psr17Factory::class, 62 | ], 63 | ]; 64 | -------------------------------------------------------------------------------- /benchmark.php: -------------------------------------------------------------------------------- 1 | createRequest(new $package[RequestFactoryInterface::class]()); 36 | $creator->createResponse(new $package[ResponseFactoryInterface::class]()); 37 | $creator->createServerRequest(new $package[ServerRequestFactoryInterface::class]()); 38 | $creator->createStream(new $package[StreamFactoryInterface::class]()); 39 | $creator->createUploadedFile( 40 | new $package[UploadedFileFactoryInterface::class](), 41 | new $package[StreamFactoryInterface::class](), 42 | ); 43 | $creator->createUri(new $package[UriFactoryInterface::class]()); 44 | } 45 | 46 | $totalTime = microtime(true) - $start; 47 | 48 | echo 'Runs: ' . number_format($runs) . PHP_EOL; 49 | echo 'Runs per second: ' . floor($runs / $totalTime) . PHP_EOL; 50 | echo 'Average time per run: ' . number_format(($totalTime / $runs) * 1000, 4) . ' ms' . PHP_EOL; 51 | echo 'Total time: ' . number_format($totalTime, 4) . ' s' . PHP_EOL; 52 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devanych/psr-http-benchmark", 3 | "description": "Benchmark of PSR-7 and PSR-17", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Evgeniy Zyubin", 9 | "email": "mail@devanych.ru", 10 | "homepage": "https://devanych.ru/", 11 | "role": "Founder and lead developer" 12 | } 13 | ], 14 | "require": { 15 | "php": "^7.4|^8.0", 16 | "guzzlehttp/psr7": "^2.6", 17 | "httpsoft/http-message": "^1.1", 18 | "laminas/laminas-diactoros": "^2.5|^3.3", 19 | "nyholm/psr7": "^1.8", 20 | "slim/psr7": "^1.3|^1.6", 21 | "f3-factory/fatfree-psr7": "^1.0" 22 | } 23 | } 24 | --------------------------------------------------------------------------------