From f6dc397a563da246a305872d9133e77872c48949 Mon Sep 17 00:00:00 2001 From: smiley Date: Fri, 27 Oct 2023 23:41:18 +0200 Subject: [PATCH] :sparkles: cheap performance tests --- .gitignore | 1 + phpunit.xml.dist | 1 + tests/Performance/PerformanceTest.php | 48 ++++++++++++++ tests/Performance/maskpattern.php | 62 ++++++++++++++++++ tests/Performance/output.php | 81 +++++++++++++++++++++++ tests/Performance/qrcode.php | 84 ++++++++++++++++++++++++ tests/Performance/qrdata.php | 92 +++++++++++++++++++++++++++ 7 files changed, 369 insertions(+) create mode 100644 tests/Performance/PerformanceTest.php create mode 100644 tests/Performance/maskpattern.php create mode 100644 tests/Performance/output.php create mode 100644 tests/Performance/qrcode.php create mode 100644 tests/Performance/qrdata.php diff --git a/.gitignore b/.gitignore index 84109c725..9cfb4de0f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ phpcs.xml phpdoc.xml phpmd.xml phpunit.xml +tests/Performance/*.json diff --git a/phpunit.xml.dist b/phpunit.xml.dist index e722021fd..0c5866732 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -9,6 +9,7 @@ ./tests/ + ./tests/Performance diff --git a/tests/Performance/PerformanceTest.php b/tests/Performance/PerformanceTest.php new file mode 100644 index 000000000..9fc9137f0 --- /dev/null +++ b/tests/Performance/PerformanceTest.php @@ -0,0 +1,48 @@ + + * @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); + } + +} diff --git a/tests/Performance/maskpattern.php b/tests/Performance/maskpattern.php new file mode 100644 index 000000000..55295b79a --- /dev/null +++ b/tests/Performance/maskpattern.php @@ -0,0 +1,62 @@ + + * @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)); diff --git a/tests/Performance/output.php b/tests/Performance/output.php new file mode 100644 index 000000000..aa26d81e3 --- /dev/null +++ b/tests/Performance/output.php @@ -0,0 +1,81 @@ + + * @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)); diff --git a/tests/Performance/qrcode.php b/tests/Performance/qrcode.php new file mode 100644 index 000000000..86436c187 --- /dev/null +++ b/tests/Performance/qrcode.php @@ -0,0 +1,84 @@ + + * @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)); diff --git a/tests/Performance/qrdata.php b/tests/Performance/qrdata.php new file mode 100644 index 000000000..0eff827ae --- /dev/null +++ b/tests/Performance/qrdata.php @@ -0,0 +1,92 @@ + + * @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));