From f8575224a07318fbae32c348c5419d3c24910273 Mon Sep 17 00:00:00 2001 From: Viktor Babanov Date: Sun, 13 Oct 2024 19:42:34 +0500 Subject: [PATCH] First benchmarks (#219) * First benchmarks * Apply fixes from StyleCI * remove debug * Add benchmark CI step --------- Co-authored-by: StyleCI Bot --- .github/workflows/bechmark.yml | 33 +++++++ composer.json | 1 + phpbench.json | 8 ++ tests/Benchmark/MetadataBench.php | 109 +++++++++++++++++++++++ tests/Benchmark/QueueBench.php | 112 ++++++++++++++++++++++++ tests/Benchmark/Support/VoidAdapter.php | 52 +++++++++++ tests/docker/php/Dockerfile | 12 +-- tests/docker/php/php.ini | 2 + 8 files changed, 324 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/bechmark.yml create mode 100644 phpbench.json create mode 100644 tests/Benchmark/MetadataBench.php create mode 100644 tests/Benchmark/QueueBench.php create mode 100644 tests/Benchmark/Support/VoidAdapter.php create mode 100644 tests/docker/php/php.ini diff --git a/.github/workflows/bechmark.yml b/.github/workflows/bechmark.yml new file mode 100644 index 00000000..36190113 --- /dev/null +++ b/.github/workflows/bechmark.yml @@ -0,0 +1,33 @@ +on: + pull_request: + paths-ignore: + - 'docs/**' + - 'README.md' + - 'CHANGELOG.md' + - '.gitignore' + - '.gitattributes' + - 'infection.json.dist' + - 'psalm.xml' + - 'tests/**' + + push: + paths-ignore: + - 'docs/**' + - 'README.md' + - 'CHANGELOG.md' + - '.gitignore' + - '.gitattributes' + - 'infection.json.dist' + - 'psalm.xml' + - 'tests/**' + +name: bechmark + +jobs: + phpbench: + uses: yiisoft/actions/.github/workflows/phpbench.yml@master + with: + os: >- + ['ubuntu-latest', 'windows-latest'] + php: >- + ['8.1'] diff --git a/composer.json b/composer.json index c59f8ca9..664f5853 100644 --- a/composer.json +++ b/composer.json @@ -46,6 +46,7 @@ }, "require-dev": { "maglnet/composer-require-checker": "^4.7", + "phpbench/phpbench": "^1.3", "phpunit/phpunit": "^10.5", "rector/rector": "^1.0.0", "roave/infection-static-analysis-plugin": "^1.34", diff --git a/phpbench.json b/phpbench.json new file mode 100644 index 00000000..04436028 --- /dev/null +++ b/phpbench.json @@ -0,0 +1,8 @@ +{ + "$schema":"./vendor/phpbench/phpbench/phpbench.schema.json", + "runner.bootstrap": "vendor/autoload.php", + "runner.path": "tests/Benchmark", + "runner.revs": 100000, + "runner.iterations": 5, + "runner.warmup": 5 +} diff --git a/tests/Benchmark/MetadataBench.php b/tests/Benchmark/MetadataBench.php new file mode 100644 index 00000000..459cd53b --- /dev/null +++ b/tests/Benchmark/MetadataBench.php @@ -0,0 +1,109 @@ + 1]); + $id = $message->getMetadata()['id']; + } + + /** + * Create metadata as object and read its value immediately + */ + #[Tag('metadata_read')] + public function benchEnvelopeRead(): void + { + $message = new IdEnvelope(new Message('foo', 'bar'), 1); + $id = $message->getId(); + } + + /** + * Create metadata as array and read its value from an envelope object + */ + #[Tag('metadata_read')] + public function benchEnvelopeReadRestored(): void + { + $message = IdEnvelope::fromMessage(new Message('foo', 'bar', ['id' => 1])); + $id = $message->getId(); + } + + public function provideEnvelopeStack(): Generator + { + $config = [1 => 'one', 5 => 'three', 15 => 'fifteen']; + $message = new IdEnvelope(new Message('foo', 'bar'), 1); + + for ($i = 1; $i <= max(...array_keys($config)); $i++) { + $message = new FailureEnvelope($message, ["fail$i" => "fail$i"]); + if (isset($config[$i])) { + yield $config[$i] => ['message' => $message]; + } + } + } + + /** + * Read metadata value from an envelope object restored from an envelope stacks of different depth + * + * @psalm-param array{message: MessageInterface} $params + */ + #[ParamProviders('provideEnvelopeStack')] + #[Tag('metadata_read')] + public function benchEnvelopeReadFromStack(array $params): void + { + $id = IdEnvelope::fromMessage($params['message'])->getId(); + } + + public function provideEnvelopeStackCounts(): Generator + { + yield 'one' => [1]; + yield 'three' => [3]; + yield 'fifteen' => [15]; + } + + /** + * Create envelope stack with the given depth + * + * @psalm-param array{0: int} $params + */ + #[ParamProviders('provideEnvelopeStackCounts')] + #[Tag('metadata_create')] + public function benchEnvelopeStackCreation(array $params): void + { + $message = new Message('foo', 'bar'); + for ($i = 0; $i < $params[0]; $i++) { + $message = new FailureEnvelope($message, ["fail$i" => "fail$i"]); + } + } + + /** + * Create metadata array with the given elements count + * + * @psalm-param array{0: int} $params + */ + #[ParamProviders('provideEnvelopeStackCounts')] + #[Tag('metadata_create')] + public function benchMetadataArrayCreation(array $params): void + { + $metadata = ['failure-meta' => []]; + for ($i = 0; $i < $params[0]; $i++) { + $metadata['failure-meta']["fail$i"] = "fail$i"; + } + $message = new Message('foo', 'bar', $metadata); + } +} diff --git a/tests/Benchmark/QueueBench.php b/tests/Benchmark/QueueBench.php new file mode 100644 index 00000000..76480523 --- /dev/null +++ b/tests/Benchmark/QueueBench.php @@ -0,0 +1,112 @@ + static function (): void { + }, + ], + $logger, + new Injector($container), + $container, + new ConsumeMiddlewareDispatcher(new MiddlewareFactoryConsume($container, $callableFactory)), + new FailureMiddlewareDispatcher( + new MiddlewareFactoryFailure($container, $callableFactory), + [], + ), + ); + $this->serializer = new JsonMessageSerializer(); + $this->adapter = new VoidAdapter($this->serializer); + + $this->queue = new Queue( + $worker, + new SimpleLoop(0), + $logger, + new PushMiddlewareDispatcher(new MiddlewareFactoryPush($container, $callableFactory)), + $this->adapter, + ); + } + + public function providePush(): Generator + { + yield 'simple' => ['message' => new Message('foo', 'bar')]; + yield 'with envelopes' => [ + 'message' => new FailureEnvelope( + new IdEnvelope( + new Message('foo', 'bar'), + 'test id', + ), + ['failure-1' => ['a', 'b', 'c']], + ), + ]; + } + + #[ParamProviders('providePush')] + #[Tag('queue_push')] + public function benchPush(array $params): void + { + $this->queue->push($params['message']); + } + + public function provideConsume(): Generator + { + yield 'simple mapping' => ['message' => $this->serializer->serialize(new Message('foo', 'bar'))]; + yield 'with envelopes mapping' => [ + 'message' => $this->serializer->serialize( + new FailureEnvelope( + new IdEnvelope( + new Message('foo', 'bar'), + 'test id', + ), + ['failure-1' => ['a', 'b', 'c']], + ), + ), + ]; + } + + #[ParamProviders('provideConsume')] + #[Tag('queue_consume')] + public function benchConsume(array $params): void + { + $this->adapter->message = $params['message']; + $this->queue->run(); + } +} diff --git a/tests/Benchmark/Support/VoidAdapter.php b/tests/Benchmark/Support/VoidAdapter.php new file mode 100644 index 00000000..edd927aa --- /dev/null +++ b/tests/Benchmark/Support/VoidAdapter.php @@ -0,0 +1,52 @@ +serializer->unserialize($this->message)); + } + + public function status(int|string $id): JobStatus + { + throw new InvalidArgumentException(); + } + + public function push(MessageInterface $message): MessageInterface + { + $this->serializer->serialize($message); + + return new IdEnvelope($message, 1); + } + + public function subscribe(callable $handlerCallback): void + { + throw new RuntimeException('Method is not implemented'); + } + + public function withChannel(string $channel): AdapterInterface + { + throw new RuntimeException('Method is not implemented'); + } +} diff --git a/tests/docker/php/Dockerfile b/tests/docker/php/Dockerfile index a2051c02..cd3e6270 100644 --- a/tests/docker/php/Dockerfile +++ b/tests/docker/php/Dockerfile @@ -1,17 +1,19 @@ # Important! Do not use this image in production! ARG PHP_VERSION -FROM --platform=linux/amd64 php:${PHP_VERSION}-cli-alpine +FROM php:${PHP_VERSION}-cli-alpine -RUN apk add git autoconf g++ make linux-headers +RUN apk add git autoconf g++ make linux-headers && \ + docker-php-ext-install pcntl && \ + pecl install xdebug pcov && \ + docker-php-ext-enable xdebug pcov -RUN docker-php-ext-install pcntl -RUN pecl install xdebug pcov -RUN docker-php-ext-enable xdebug pcov +ADD ./tests/docker/php/php.ini /usr/local/etc/php/conf.d/40-custom.ini COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer ENV COMPOSER_ALLOW_SUPERUSER 1 WORKDIR /app +RUN git config --global --add safe.directory /app ENTRYPOINT ["sh", "tests/docker/php/entrypoint.sh"] CMD ["sleep", "infinity"] diff --git a/tests/docker/php/php.ini b/tests/docker/php/php.ini new file mode 100644 index 00000000..18fdd3e5 --- /dev/null +++ b/tests/docker/php/php.ini @@ -0,0 +1,2 @@ +opcache.enable=1 +opcache.enable_cli=1