Browse Source

:sparkles: cheap performance tests

smiley 2 năm trước cách đây
mục cha
commit
f6dc397a56

+ 1 - 0
.gitignore

@@ -18,3 +18,4 @@ phpcs.xml
 phpdoc.xml
 phpmd.xml
 phpunit.xml
+tests/Performance/*.json

+ 1 - 0
phpunit.xml.dist

@@ -9,6 +9,7 @@
 	<testsuites>
 		<testsuite name="php-qrcode test suite">
 			<directory suffix=".php">./tests/</directory>
+			<exclude>./tests/Performance</exclude>
 		</testsuite>
 	</testsuites>
 	<coverage processUncoveredFiles="true">

+ 48 - 0
tests/Performance/PerformanceTest.php

@@ -0,0 +1,48 @@
+<?php
+/**
+ * Class PerformanceTest
+ *
+ * @created      16.10.2023
+ * @author       smiley <smiley@chillerlan.net>
+ * @copyright    2023 smiley
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCodeTest\Performance;
+
+use Closure;
+use function hrtime;
+
+/**
+ *
+ */
+class PerformanceTest{
+
+	protected int $runs;
+	protected int $total = 0;
+
+	public function __construct(int $runs = 1000){
+		$this->runs = $runs;
+	}
+
+	public function run(Closure $subject):self{
+		$this->total = 0;
+
+		for($i = 0; $i < $this->runs; $i++){
+			$start = hrtime(true);
+
+			$subject();
+
+			$end = hrtime(true);
+
+			$this->total += ($end - $start);
+		}
+
+		return $this;
+	}
+
+	public function getResult():float{
+		return ($this->total / $this->runs / 1000000);
+	}
+
+}

+ 62 - 0
tests/Performance/maskpattern.php

@@ -0,0 +1,62 @@
+<?php
+/**
+ * Tests the performance of the mask pattern penalty testing
+ *
+ * @created      18.10.2023
+ * @author       smiley <smiley@chillerlan.net>
+ * @copyright    2023 smiley
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCodeTest\Performance;
+
+use chillerlan\QRCode\{QRCode, QROptions};
+use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Mode, Version};
+use chillerlan\QRCodeTest\QRMaxLengthTrait;
+use Generator;
+use function file_put_contents;
+use function json_encode;
+use function printf;
+use function sprintf;
+use function str_repeat;
+use function substr;
+use const JSON_PRETTY_PRINT;
+
+require_once __DIR__.'/../../vendor/autoload.php';
+
+// excerpt from QRCodeReaderTestAbstract
+$generator = new class () {
+	use QRMaxLengthTrait;
+
+	public function dataProvider():Generator{
+		$str      = str_repeat('https://www.youtube.com/watch?v=dQw4w9WgXcQ ', 100);
+		$eccLevel = new EccLevel(EccLevel::H);
+
+		for($v = 1; $v <= 40; $v++){
+			$version = new Version($v);
+
+			yield sprintf('version %2s%s', $version, $eccLevel) => [
+				$version->getVersionNumber(),
+				$eccLevel->getLevel(),
+				substr($str, 0, self::getMaxLengthForMode(Mode::BYTE, $version, $eccLevel)),
+			];
+		}
+	}
+
+};
+
+$test = new PerformanceTest(100);
+$json = [];
+
+foreach($generator->dataProvider() as $key => [$version, $eccLevel, $data]){
+	$qrcode = new QRCode(new QROptions(['version' => $version, 'eccLevel' => $eccLevel]));
+	$qrcode->addByteSegment($data);
+	$matrix = $qrcode->getQRMatrix();
+
+	$test->run(fn() => MaskPattern::getBestPattern($matrix));
+
+	printf("%s: %01.3fms\n", $key, $test->getResult());
+	$json[$version] = $test->getResult();
+}
+
+file_put_contents(__DIR__.'/performance_maskpattern.json', json_encode($json, JSON_PRETTY_PRINT));

+ 81 - 0
tests/Performance/output.php

@@ -0,0 +1,81 @@
+<?php
+/**
+ * Tests the performance of the built-in output classes
+ *
+ * @created      16.10.2023
+ * @author       smiley <smiley@chillerlan.net>
+ * @copyright    2023 smiley
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCodeTest\Performance;
+
+use chillerlan\QRCode\{QRCode, QROptions};
+use chillerlan\QRCode\Common\{EccLevel, Mode, Version};
+use chillerlan\QRCode\Output\QROutputInterface;
+use chillerlan\QRCodeTest\QRMaxLengthTrait;
+use Generator;
+use function file_put_contents;
+use function json_encode;
+use function printf;
+use function sprintf;
+use function str_repeat;
+use function str_replace;
+use function substr;
+use const JSON_PRETTY_PRINT;
+
+require_once __DIR__.'/../../vendor/autoload.php';
+
+
+// excerpt from QRCodeReaderTestAbstract
+$generator = new class () {
+	use QRMaxLengthTrait;
+
+	public function dataProvider():Generator{
+		$str      = str_repeat('https://www.youtube.com/watch?v=dQw4w9WgXcQ ', 100);
+		$eccLevel = new EccLevel(EccLevel::L);
+
+		for($v = 5; $v <= 40; $v += 5){
+			$version  = new Version($v);
+			foreach(QROutputInterface::MODES as $outputType => $FQN){
+				$name = str_replace('chillerlan\\QRCode\\Output\\', '', $FQN);
+
+				yield sprintf('version %2s: %-14s', $version, $name) => [
+					$version->getVersionNumber(),
+					$outputType,
+					$FQN,
+					substr($str, 0, self::getMaxLengthForMode(Mode::BYTE, $version, $eccLevel)),
+					$name,
+				];
+			}
+		}
+
+	}
+
+};
+
+$test = new PerformanceTest(100);
+$json = [];
+
+foreach($generator->dataProvider() as $key => [$version, $outputType, $FQN, $data, $name]){
+
+	$options = new QROptions([
+		'version'             => $version,
+		'outputType'          => $outputType,
+		'connectPaths'        => true,
+		'drawLightModules'    => true,
+		'drawCircularModules' => true,
+		'gdImageUseUpscale'   => false, // set to false to allow proper comparison
+	]);
+
+	$qrcode = new QRCode($options);
+	$qrcode->addByteSegment($data);
+	$matrix = $qrcode->getQRMatrix();
+
+	$test->run(fn() => (new ($FQN)($options, $matrix))->dump());
+
+	printf("%s: %8.3fms\n", $key, $test->getResult());
+	$json[$name][$version] = $test->getResult();
+}
+
+file_put_contents(__DIR__.'/performance_output.json', json_encode($json, JSON_PRETTY_PRINT));

+ 84 - 0
tests/Performance/qrcode.php

@@ -0,0 +1,84 @@
+<?php
+/**
+ * Tests the overall performance of the QRCode class
+ *
+ * @created      19.10.2023
+ * @author       smiley <smiley@chillerlan.net>
+ * @copyright    2023 smiley
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCodeTest\Performance;
+
+use chillerlan\QRCode\{QRCode, QROptions};
+use chillerlan\QRCode\Common\{EccLevel, Mode, Version};
+use chillerlan\QRCode\Output\QROutputInterface;
+use chillerlan\QRCodeTest\QRMaxLengthTrait;
+use Generator;
+use function file_put_contents;
+use function json_encode;
+use function mb_substr;
+use function printf;
+use function sprintf;
+use function str_repeat;
+use function str_replace;
+use const JSON_PRETTY_PRINT;
+
+require_once __DIR__.'/../../vendor/autoload.php';
+
+// excerpt from QRCodeReaderTestAbstract
+$generator = new class () {
+	use QRMaxLengthTrait;
+
+	public function dataProvider():Generator{
+
+		$dataModeData = [
+			Mode::NUMBER   => str_repeat('0123456789', 750),
+			Mode::ALPHANUM => str_repeat('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', 100),
+			Mode::KANJI    => str_repeat('漂う花の香り', 350),
+			Mode::HANZI    => str_repeat('无可奈何燃花作香', 250),
+			Mode::BYTE     => str_repeat('https://www.youtube.com/watch?v=dQw4w9WgXcQ ', 100),
+		];
+
+		foreach(Mode::INTERFACES as $dataMode => $dataModeInterface){
+			$dataModeName = str_replace('chillerlan\\QRCode\\Data\\', '', $dataModeInterface);
+
+			for($v = 1; $v <= 40; $v++){
+				$version = new Version($v);
+
+				foreach([EccLevel::L, EccLevel::M, EccLevel::Q, EccLevel::H] as $ecc){
+					$eccLevel = new EccLevel($ecc);
+
+					yield sprintf('version %2s%s (%s)', $version, $eccLevel, $dataModeName) => [
+						$version->getVersionNumber(),
+						$eccLevel,
+						$dataModeInterface,
+						$dataModeName,
+						mb_substr($dataModeData[$dataMode], 0, self::getMaxLengthForMode($dataMode, $version, $eccLevel)),
+					];
+				}
+			}
+		}
+
+	}
+
+};
+
+$test = new PerformanceTest(100);
+$json = [];
+
+foreach($generator->dataProvider() as $key => [$version, $eccLevel, $dataModeInterface, $dataModeName, $data]){
+
+	$options = new QROptions([
+		'outputType' => QROutputInterface::STRING_JSON,
+		'version'    => $version,
+		'eccLevel'   => $eccLevel->getLevel(),
+	]);
+
+	$test->run(fn() => (new QRCode($options))->addSegment(new ($dataModeInterface)($data))->render());
+
+	printf("%s: %01.3fms\n", $key, $test->getResult());
+	$json[$dataModeName][(string)$eccLevel][$version] = $test->getResult();
+}
+
+file_put_contents(__DIR__.'/performance_qrcode.json', json_encode($json, JSON_PRETTY_PRINT));

+ 92 - 0
tests/Performance/qrdata.php

@@ -0,0 +1,92 @@
+<?php
+/**
+ * Tests the QRMatrix write performance
+ *
+ * @created      16.10.2023
+ * @author       smiley <smiley@chillerlan.net>
+ * @copyright    2023 smiley
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCodeTest\Performance;
+
+use chillerlan\QRCode\Common\{EccLevel, Mode, Version};
+use chillerlan\QRCode\Data\QRData;
+use chillerlan\QRCode\QROptions;
+use chillerlan\QRCodeTest\QRMaxLengthTrait;
+use Generator;
+use function file_put_contents;
+use function json_encode;
+use function printf;
+use function sprintf;
+use function str_repeat;
+use function str_replace;
+use const JSON_PRETTY_PRINT;
+
+require_once __DIR__.'/../../vendor/autoload.php';
+
+// excerpt from QRCodeReaderTestAbstract
+$generator = new class () {
+	use QRMaxLengthTrait;
+
+	public function dataProvider():Generator{
+
+		$dataModeData = [
+			Mode::NUMBER   => str_repeat('0123456789', 750),
+			Mode::ALPHANUM => str_repeat('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', 100),
+			Mode::KANJI    => str_repeat('漂う花の香り', 350),
+			Mode::HANZI    => str_repeat('无可奈何燃花作香', 250),
+			Mode::BYTE     => str_repeat('https://www.youtube.com/watch?v=dQw4w9WgXcQ ', 100),
+		];
+
+		foreach(Mode::INTERFACES as $dataMode => $dataModeInterface){
+			$dataModeName = str_replace('chillerlan\\QRCode\\Data\\', '', $dataModeInterface);
+
+			for($v = 1; $v <= 40; $v++){
+				$version = new Version($v);
+
+				foreach([EccLevel::L, EccLevel::M, EccLevel::Q, EccLevel::H] as $ecc){
+					$eccLevel = new EccLevel($ecc);
+
+					yield sprintf('version %2s%s (%s)', $version, $eccLevel, $dataModeName) => [
+						$version->getVersionNumber(),
+						$eccLevel,
+						$dataModeInterface,
+						$dataModeName,
+						mb_substr($dataModeData[$dataMode], 0, self::getMaxLengthForMode($dataMode, $version, $eccLevel)),
+					];
+				}
+			}
+		}
+
+	}
+
+};
+
+$test = new PerformanceTest(100);
+$json = [];
+
+foreach($generator->dataProvider() as $key => [$version, $eccLevel, $dataModeInterface, $dataModeName, $data]){
+	// invovcation tests the performance of QRData::writeBitBuffer()
+	$test->run(fn() => new QRData(new QROptions(['version' => $version, 'eccLevel' => $eccLevel->getLevel()]), [new ($dataModeInterface)($data)]));
+
+	printf('%s encode: % 6.3fms', $key, $test->getResult());
+	$json[$dataModeName][(string)$eccLevel]['encode'][$version] = $test->getResult();
+
+	// writeMatrix includes QRMatrix::writeCodewords() and the ReedSolomonEncoder
+	$qrdata = new QRData(new QROptions(['version' => $version, 'eccLevel' => $eccLevel->getLevel()]), [new ($dataModeInterface)($data)]);
+	$test->run(fn() => $qrdata->writeMatrix());
+
+	printf(', write matrix: % 6.3fms', $test->getResult());
+	$json[$dataModeName][(string)$eccLevel]['write'][$version] = $test->getResult();
+
+	$bitBuffer = $qrdata->getBitBuffer();
+	$bitBuffer->read(4); // read data mode indicator
+
+	$test->run(fn() => $dataModeInterface::decodeSegment(clone $bitBuffer, $version));
+
+	printf(", decode: % 6.3fms\n", $test->getResult());
+	$json[$dataModeName][(string)$eccLevel]['decode'][$version] = $test->getResult();
+}
+
+file_put_contents(__DIR__.'/performance_qrdata.json', json_encode($json, JSON_PRETTY_PRINT));