mirror of
https://github.com/chillerlan/php-qrcode.git
synced 2026-08-18 20:40:05 +00:00
✨ add PHPBench to run benchmarks
This commit is contained in:
@@ -25,6 +25,7 @@ return [
|
||||
// Thus, both first-party and third-party code being used by
|
||||
// your application should be included in this list.
|
||||
'directory_list' => [
|
||||
'benchmark',
|
||||
'examples',
|
||||
'src',
|
||||
'tests',
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
/**
|
||||
* Class BenchmarkAbstract
|
||||
*
|
||||
* @created 23.04.2024
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2024 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace chillerlan\QRCodeBenchmark;
|
||||
|
||||
use chillerlan\QRCode\{QRCode, QROptions};
|
||||
use chillerlan\QRCode\Common\{EccLevel, Mode, Version};
|
||||
use chillerlan\QRCode\Data\QRMatrix;
|
||||
use chillerlan\QRCodeTest\QRMaxLengthTrait;
|
||||
use PhpBench\Attributes\{Iterations, ParamProviders, Revs, Warmup};
|
||||
use Generator, RuntimeException;
|
||||
use function extension_loaded, is_dir, mb_substr, mkdir, sprintf, str_repeat, str_replace;
|
||||
|
||||
/**
|
||||
* The abstract benchmark with common methods
|
||||
*/
|
||||
#[Iterations(3)]
|
||||
#[Warmup(3)]
|
||||
#[Revs(100)]
|
||||
#[ParamProviders(['versionProvider', 'eccLevelProvider', 'dataModeProvider'])]
|
||||
abstract class BenchmarkAbstract{
|
||||
use QRMaxLengthTrait;
|
||||
|
||||
protected const BUILDDIR = __DIR__.'/../.build/phpbench/';
|
||||
protected const ECC_LEVELS = [EccLevel::L, EccLevel::M, EccLevel::Q, EccLevel::H];
|
||||
protected const DATAMODES = Mode::INTERFACES;
|
||||
|
||||
protected array $dataModeData;
|
||||
protected string $testData;
|
||||
protected QROptions $options;
|
||||
protected QRMatrix $matrix;
|
||||
|
||||
// properties from data providers
|
||||
protected Version $version;
|
||||
protected EccLevel $eccLevel;
|
||||
protected int $mode;
|
||||
protected string $modeFQCN;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct(){
|
||||
|
||||
foreach(['gd', 'imagick'] as $ext){
|
||||
if(!extension_loaded($ext)){
|
||||
throw new RuntimeException(sprintf('ext-%s not loaded', $ext));
|
||||
}
|
||||
}
|
||||
|
||||
if(!is_dir(self::BUILDDIR)){
|
||||
mkdir(directory: self::BUILDDIR, recursive: true);
|
||||
}
|
||||
|
||||
$this->dataModeData = $this->generateDataModeData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates test data strings for each mode
|
||||
*/
|
||||
protected function generateDataModeData():array{
|
||||
return [
|
||||
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),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a test max-length data string for the given version, ecc level and data mode
|
||||
*/
|
||||
protected function getData(Version $version, EccLevel $eccLevel, int $mode):string{
|
||||
$maxLength = self::getMaxLengthForMode($mode, $version, $eccLevel);
|
||||
|
||||
if($mode === Mode::KANJI || $mode === Mode::HANZI){
|
||||
return mb_substr($this->dataModeData[$mode], 0, $maxLength);
|
||||
}
|
||||
|
||||
return mb_substr($this->dataModeData[$mode], 0, $maxLength, '8bit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a QROptions instance and assigns it to its temp property
|
||||
*/
|
||||
protected function initQROptions(array $options):void{
|
||||
$this->options = new QROptions($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a QRMatrix instance and assigns it to its temp property
|
||||
*/
|
||||
public function initMatrix():void{
|
||||
$this->matrix = (new QRCode($this->options))
|
||||
->addByteSegment($this->testData)
|
||||
->getQRMatrix()
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a test data string and assigns it to its temp property
|
||||
*/
|
||||
public function generateTestData():void{
|
||||
$this->testData = $this->getData($this->version, $this->eccLevel, $this->mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the parameter array from the providers to properties and enforces the types
|
||||
*/
|
||||
public function assignParams(array $params):void{
|
||||
foreach($params as $k => $v){
|
||||
$this->{$k} = $v;
|
||||
}
|
||||
}
|
||||
|
||||
public function versionProvider():Generator{
|
||||
for($v = 1; $v <= 40; $v++){
|
||||
$version = new Version($v);
|
||||
|
||||
yield (string)$version => ['version' => $version];
|
||||
}
|
||||
}
|
||||
|
||||
public function eccLevelProvider():Generator{
|
||||
foreach(static::ECC_LEVELS as $ecc){
|
||||
$eccLevel = new EccLevel($ecc);
|
||||
|
||||
yield (string)$eccLevel => ['eccLevel' => $eccLevel];
|
||||
}
|
||||
}
|
||||
|
||||
public function dataModeProvider():Generator{
|
||||
foreach(static::DATAMODES as $mode => $modeFQCN){
|
||||
$name = str_replace('chillerlan\\QRCode\\Data\\', '', $modeFQCN);
|
||||
|
||||
yield $name => ['mode' => $mode, 'modeFQCN' => $modeFQCN];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* Class DecoderBenchmark
|
||||
*
|
||||
* @created 26.04.2024
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2024 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace chillerlan\QRCodeBenchmark;
|
||||
|
||||
use chillerlan\QRCode\Common\{GDLuminanceSource, IMagickLuminanceSource, Mode};
|
||||
use chillerlan\QRCode\Data\Byte;
|
||||
use chillerlan\QRCode\Decoder\{Decoder, DecoderResult};
|
||||
use chillerlan\QRCode\Output\QRGdImagePNG;
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
use PhpBench\Attributes\{AfterMethods, BeforeMethods, Subject};
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Tests the performance of the QR Code reader/decoder
|
||||
*/
|
||||
final class DecoderBenchmark extends BenchmarkAbstract{
|
||||
|
||||
protected const DATAMODES = [Mode::BYTE => Byte::class];
|
||||
|
||||
private string $imageBlob;
|
||||
private DecoderResult $result;
|
||||
|
||||
public function initOptions():void{
|
||||
|
||||
$options = [
|
||||
'version' => $this->version->getVersionNumber(),
|
||||
'eccLevel' => $this->eccLevel->getLevel(),
|
||||
'scale' => 2,
|
||||
'imageTransparent' => false,
|
||||
'outputBase64' => false,
|
||||
];
|
||||
|
||||
$this->initQROptions($options);
|
||||
}
|
||||
|
||||
public function generateImageBlob():void{
|
||||
$this->imageBlob = (new QRGdImagePNG($this->options, $this->matrix))->dump();
|
||||
}
|
||||
|
||||
public function checkReaderResult():void{
|
||||
if($this->result->data !== $this->testData){
|
||||
throw new RuntimeException('invalid reader result');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the performance of the GD reader
|
||||
*/
|
||||
#[Subject]
|
||||
#[BeforeMethods(['assignParams', 'generateTestData', 'initOptions', 'initMatrix', 'generateImageBlob'])]
|
||||
#[AfterMethods(['checkReaderResult'])]
|
||||
public function GDLuminanceSource():void{
|
||||
|
||||
// in rare cases the reader will be unable to detect and throw,
|
||||
// but we don't want the performance test to yell about it
|
||||
// @see QRCodeReaderTestAbstract::testReadData()
|
||||
try{
|
||||
$this->result = (new Decoder($this->options))
|
||||
->decode(GDLuminanceSource::fromBlob($this->imageBlob, $this->options));
|
||||
}
|
||||
catch(QRCodeException){
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the performance of the ImageMagick reader
|
||||
*/
|
||||
#[Subject]
|
||||
#[BeforeMethods(['assignParams', 'generateTestData', 'initOptions', 'initMatrix', 'generateImageBlob'])]
|
||||
#[AfterMethods(['checkReaderResult'])]
|
||||
public function IMagickLuminanceSource():void{
|
||||
$this->options->readerUseImagickIfAvailable = true;
|
||||
|
||||
try{
|
||||
$this->result = (new Decoder($this->options))
|
||||
->decode(IMagickLuminanceSource::fromBlob($this->imageBlob, $this->options));
|
||||
}
|
||||
catch(QRCodeException){
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Class MaskPatternBenchmark
|
||||
*
|
||||
* @created 23.04.2024
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2024 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace chillerlan\QRCodeBenchmark;
|
||||
|
||||
use chillerlan\QRCode\Common\{MaskPattern, Mode};
|
||||
use chillerlan\QRCode\Data\Byte;
|
||||
use PhpBench\Attributes\{BeforeMethods, Subject};
|
||||
|
||||
/**
|
||||
* Tests the performance of the mask pattern penalty testing
|
||||
*/
|
||||
final class MaskPatternBenchmark extends BenchmarkAbstract{
|
||||
|
||||
protected const DATAMODES = [Mode::BYTE => Byte::class];
|
||||
|
||||
public function initOptions():void{
|
||||
|
||||
$options = [
|
||||
'version' => $this->version->getVersionNumber(),
|
||||
'eccLevel' => $this->eccLevel->getLevel(),
|
||||
];
|
||||
|
||||
$this->initQROptions($options);
|
||||
}
|
||||
|
||||
#[Subject]
|
||||
#[BeforeMethods(['assignParams', 'generateTestData', 'initOptions', 'initMatrix'])]
|
||||
public function getBestPattern():void{
|
||||
MaskPattern::getBestPattern($this->matrix);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* Class OutputBenchmark
|
||||
*
|
||||
* @created 23.04.2024
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2024 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace chillerlan\QRCodeBenchmark;
|
||||
|
||||
use chillerlan\QRCode\Common\{EccLevel, Mode};
|
||||
use chillerlan\QRCode\Data\Byte;
|
||||
use chillerlan\QRCode\Output\{
|
||||
QREps, QRFpdf, QRGdImageJPEG, QRGdImagePNG, QRGdImageWEBP, QRImagick, QRMarkupSVG, QRStringJSON
|
||||
};
|
||||
use PhpBench\Attributes\{BeforeMethods, Subject};
|
||||
|
||||
/**
|
||||
* Tests the performance of the built-in output classes
|
||||
*/
|
||||
#[BeforeMethods(['assignParams', 'generateTestData', 'initOptions', 'initMatrix'])]
|
||||
final class OutputBenchmark extends BenchmarkAbstract{
|
||||
|
||||
protected const ECC_LEVELS = [EccLevel::H];
|
||||
protected const DATAMODES = [Mode::BYTE => Byte::class];
|
||||
|
||||
public function initOptions():void{
|
||||
|
||||
$options = [
|
||||
'version' => $this->version->getVersionNumber(),
|
||||
'eccLevel' => $this->eccLevel->getLevel(),
|
||||
'connectPaths' => true,
|
||||
'drawLightModules' => true,
|
||||
'drawCircularModules' => true,
|
||||
'gdImageUseUpscale' => false, // set to false to allow proper comparison
|
||||
];
|
||||
|
||||
$this->initQROptions($options);
|
||||
}
|
||||
|
||||
#[Subject]
|
||||
public function QREps():void{
|
||||
(new QREps($this->options, $this->matrix))->dump();
|
||||
}
|
||||
|
||||
#[Subject]
|
||||
public function QRFpdf():void{
|
||||
(new QRFpdf($this->options, $this->matrix))->dump();
|
||||
}
|
||||
|
||||
/**
|
||||
* for some reason imageavif() is extremely slow, ~50x slower than imagepng()
|
||||
*/
|
||||
# #[Subject]
|
||||
# public function QRGdImageAVIF():void{
|
||||
# (new QRGdImageAVIF($this->options, $this->matrix))->dump();
|
||||
# }
|
||||
|
||||
#[Subject]
|
||||
public function QRGdImageJPEG():void{
|
||||
(new QRGdImageJPEG($this->options, $this->matrix))->dump();
|
||||
}
|
||||
|
||||
#[Subject]
|
||||
public function QRGdImagePNG():void{
|
||||
(new QRGdImagePNG($this->options, $this->matrix))->dump();
|
||||
}
|
||||
|
||||
#[Subject]
|
||||
public function QRGdImageWEBP():void{
|
||||
(new QRGdImageWEBP($this->options, $this->matrix))->dump();
|
||||
}
|
||||
|
||||
#[Subject]
|
||||
public function QRImagick():void{
|
||||
(new QRImagick($this->options, $this->matrix))->dump();
|
||||
}
|
||||
|
||||
#[Subject]
|
||||
public function QRMarkupSVG():void{
|
||||
(new QRMarkupSVG($this->options, $this->matrix))->dump();
|
||||
}
|
||||
|
||||
#[Subject]
|
||||
public function QRStringJSON():void{
|
||||
(new QRStringJSON($this->options, $this->matrix))->dump();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* Class QRCodeBenchmark
|
||||
*
|
||||
* @created 23.04.2024
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2024 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace chillerlan\QRCodeBenchmark;
|
||||
|
||||
use chillerlan\QRCode\Output\QRStringJSON;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use PhpBench\Attributes\{BeforeMethods, Subject};
|
||||
|
||||
/**
|
||||
* Tests the overall performance of the QRCode class
|
||||
*/
|
||||
final class QRCodeBenchmark extends BenchmarkAbstract{
|
||||
|
||||
public function initOptions():void{
|
||||
|
||||
$options = [
|
||||
'outputInterface' => QRStringJSON::class,
|
||||
'version' => $this->version->getVersionNumber(),
|
||||
'eccLevel' => $this->eccLevel->getLevel(),
|
||||
];
|
||||
|
||||
$this->initQROptions($options);
|
||||
}
|
||||
|
||||
#[Subject]
|
||||
#[BeforeMethods(['assignParams', 'generateTestData', 'initOptions'])]
|
||||
public function render():void{
|
||||
(new QRCode($this->options))->addSegment(new $this->modeFQCN($this->testData))->render();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* Class QRDataBenchmark
|
||||
*
|
||||
* @created 23.04.2024
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2024 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace chillerlan\QRCodeBenchmark;
|
||||
|
||||
use chillerlan\QRCode\Common\BitBuffer;
|
||||
use chillerlan\QRCode\Data\QRData;
|
||||
use PhpBench\Attributes\{BeforeMethods, Subject};
|
||||
|
||||
/**
|
||||
* Tests the QRMatrix write performance
|
||||
*/
|
||||
final class QRDataBenchmark extends BenchmarkAbstract{
|
||||
|
||||
private QRData $qrData;
|
||||
private BitBuffer $bitBuffer;
|
||||
|
||||
public function initOptions():void{
|
||||
|
||||
$options = [
|
||||
'version' => $this->version->getVersionNumber(),
|
||||
'eccLevel' => $this->eccLevel->getLevel(),
|
||||
];
|
||||
|
||||
$this->initQROptions($options);
|
||||
}
|
||||
|
||||
public function initQRData():void{
|
||||
$this->qrData = new QRData($this->options, [new $this->modeFQCN($this->testData)]);
|
||||
}
|
||||
|
||||
public function initBitBuffer():void{
|
||||
$this->bitBuffer = $this->qrData->getBitBuffer();
|
||||
$this->bitBuffer->read(4); // read data mode indicator
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the performance of QRData invovcation, includes QRData::writeBitBuffer()
|
||||
*/
|
||||
#[Subject]
|
||||
#[BeforeMethods(['assignParams', 'generateTestData', 'initOptions'])]
|
||||
public function invocation():void{
|
||||
/** @phan-suppress-next-line PhanNoopNew */
|
||||
new QRData($this->options, [new $this->modeFQCN($this->testData)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the performance of QRData::writeMatrix(), includes QRMatrix::writeCodewords() and the ReedSolomonEncoder
|
||||
*/
|
||||
#[Subject]
|
||||
#[BeforeMethods(['assignParams', 'generateTestData', 'initOptions', 'initQRData'])]
|
||||
public function writeMatrix():void{
|
||||
$this->qrData->writeMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the performance of QRDataModeInterface::decodeSegment()
|
||||
*
|
||||
* we need to clone the BitBuffer instance here because its internal state is modified during decoding
|
||||
*/
|
||||
#[Subject]
|
||||
#[BeforeMethods(['assignParams', 'generateTestData', 'initOptions', 'initQRData', 'initBitBuffer'])]
|
||||
public function decodeSegment():void{
|
||||
/** @noinspection PhpUndefinedMethodInspection */
|
||||
$this->modeFQCN::decodeSegment(clone $this->bitBuffer, $this->version->getVersionNumber());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
body {
|
||||
font-size: 16px;
|
||||
line-height: 1.25em;
|
||||
font-family: "Verdana", sans-serif;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
code{
|
||||
font-family: "Consolas", monospace;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
td{
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
|
||||
thead > tr:nth-child(1) > th:nth-child(1n+2),
|
||||
thead > tr:nth-child(even) > th:nth-child(4n+2),
|
||||
tbody > tr > td:nth-child(4n+2) {
|
||||
border-left: 2px solid #999;
|
||||
}
|
||||
|
||||
tr:nth-child(odd),
|
||||
thead > tr > th:nth-child(even),
|
||||
thead > tr:nth-child(2) > th:first-child {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
tbody > tr > td:first-child {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tbody > tr > td:nth-child(1n+2) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
thead > tr:nth-child(2) > th:nth-child(4n+2),
|
||||
tbody > tr > td:nth-child(4n+2) {
|
||||
background: rgba(0, 255, 0, 0.1);
|
||||
}
|
||||
|
||||
thead > tr:nth-child(2) > th:nth-child(4n+3),
|
||||
tbody > tr > td:nth-child(4n+3) {
|
||||
background: rgba(255, 255, 0, 0.1);
|
||||
}
|
||||
|
||||
thead > tr:nth-child(2) > th:nth-child(4n+4),
|
||||
tbody > tr > td:nth-child(4n+4) {
|
||||
background: rgba(0, 0, 255, 0.1);
|
||||
}
|
||||
|
||||
thead > tr:nth-child(2) > th:nth-child(4n+5),
|
||||
tbody > tr > td:nth-child(4n+5) {
|
||||
background: rgba(255, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
a.return{
|
||||
display: block;
|
||||
margin: 1em 0;
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
/**
|
||||
* Generates a benchmark report in HTML
|
||||
*
|
||||
* @created 30.04.2024
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2024 smiley
|
||||
* @license MIT
|
||||
*
|
||||
* @phan-file-suppress PhanTypeArraySuspiciousNullable
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace chillerlan\QRCodeBenchmark;
|
||||
|
||||
use RuntimeException;
|
||||
use function array_keys;
|
||||
use function copy;
|
||||
use function file_exists;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function htmlspecialchars;
|
||||
use function implode;
|
||||
use function is_bool;
|
||||
use function is_dir;
|
||||
use function json_decode;
|
||||
use function mkdir;
|
||||
use function sprintf;
|
||||
|
||||
require_once __DIR__.'/parse-common.php';
|
||||
|
||||
if(!file_exists(FILE.'.json')){
|
||||
throw new RuntimeException('invalid benchmark report');
|
||||
}
|
||||
|
||||
$json = json_decode(file_get_contents(FILE.'.json'), true);
|
||||
|
||||
$htmlHead = '<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<link rel="stylesheet" href="./benchmark.css">
|
||||
<title>%s results</title>
|
||||
</head>
|
||||
<body>';
|
||||
|
||||
$htmlFoot = '</body>
|
||||
</html>';
|
||||
|
||||
$html = [];
|
||||
|
||||
// General information/overview
|
||||
|
||||
$env = $json['env'];
|
||||
$suite = $json['suite'];
|
||||
|
||||
$html['index'][] = sprintf($htmlHead, 'Benchmark');
|
||||
$html['index'][] = '<h1>Benchmark results</h1>';
|
||||
$html['index'][] = '<h2>Environment</h2>';
|
||||
$html['index'][] = '<table><thead>';
|
||||
$html['index'][] = '<tr><th>Name</th><th>Value</th></tr>';
|
||||
$html['index'][] = '</thead><tbody>';
|
||||
|
||||
$html['index'][] = sprintf('<tr><td>date</td><td style="text-align: left;">%s %s</td></tr>', $suite['date'], $suite['time']);
|
||||
$html['index'][] = sprintf('<tr><td>environment</td><td style="text-align: left;">%s %s, %s</td></tr>', $env['uname_os'], $env['uname_version'], $env['uname_machine']);
|
||||
$html['index'][] = sprintf('<tr><td>tag</td><td style="text-align: left;">%s</td></tr>', htmlspecialchars($suite['tag']));
|
||||
|
||||
foreach(['php_version', 'php_ini', 'php_extensions', 'php_xdebug', 'opcache_extension_loaded', 'opcache_enabled'] as $field){
|
||||
|
||||
// prettify the boolean values
|
||||
if(is_bool($env[$field])){
|
||||
$env[$field] = ($env[$field]) ? '✓' : '✗';
|
||||
}
|
||||
|
||||
$html['index'][] = sprintf('<tr><td>%s</td><td style="text-align: left;">%s</td></tr>', $field, $env[$field]);
|
||||
}
|
||||
|
||||
$html['index'][] = '</tbody></table>';
|
||||
|
||||
// list indiviidual reports
|
||||
$html['index'][] = '<h2>Reports</h2>';
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach(array_keys($json['benchmark']) as $benchmark){
|
||||
|
||||
// add a file & header
|
||||
$html[$benchmark][] = sprintf($htmlHead, $benchmark);
|
||||
$html[$benchmark][] = sprintf('<h1>%s</h1>', $benchmark);
|
||||
$html[$benchmark][] = sprintf('<code>%s</code>', $json['benchmark'][$benchmark]['class']);
|
||||
|
||||
foreach(array_keys($json['benchmark'][$benchmark]['subjects']) as $subject){
|
||||
// list item
|
||||
$list[$benchmark][] = $subject;
|
||||
|
||||
$subj = $json['benchmark'][$benchmark]['subjects'][$subject];
|
||||
|
||||
$html[$benchmark][] = sprintf('<h2 id="%1$s">%1$s</h2>', $subject);
|
||||
$html[$benchmark][] = sprintf('<div>Revs: %s, Iterations: %s</div>', $subj['revs'], $subj['iterations']);
|
||||
$html[$benchmark][] = parseVariants($subj['variants']);
|
||||
}
|
||||
|
||||
// close document
|
||||
$html[$benchmark][] = '<a class="return" href="./index.html">back to overview</a>';
|
||||
$html[$benchmark][] = $htmlFoot;
|
||||
}
|
||||
|
||||
// create overview list
|
||||
|
||||
$html['index'][] = '<ul>';
|
||||
|
||||
foreach($list as $benchmark => $subjects){
|
||||
// list item
|
||||
$html['index'][] = sprintf('<li><a href="./%1$s.html">%1$s</a><ul>', $benchmark);
|
||||
|
||||
foreach($subjects as $subject){
|
||||
// list sub-item
|
||||
$html['index'][] = sprintf('<li><a href="./%1$s.html#%2$s">%2$s</a></li>', $benchmark, $subject);
|
||||
}
|
||||
|
||||
$html['index'][] = '</ul></li>';
|
||||
}
|
||||
|
||||
$html['index'][] = '</ul>';
|
||||
|
||||
// close document
|
||||
$html['index'][] = $htmlFoot;
|
||||
|
||||
if(!is_dir(BUILDDIR.'/html/')){
|
||||
mkdir(BUILDDIR.'/html/');
|
||||
}
|
||||
|
||||
copy('./benchmark.css', BUILDDIR.'/html/benchmark.css');
|
||||
|
||||
foreach($html as $file => $content){
|
||||
file_put_contents(BUILDDIR.'/html/'.$file.'.html', implode("\n", $content));
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* Generates a benchmark report in Markdown
|
||||
*
|
||||
* @created 27.04.2024
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2024 smiley
|
||||
* @license MIT
|
||||
*
|
||||
* @phan-file-suppress PhanTypeArraySuspiciousNullable
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace chillerlan\QRCodeBenchmark;
|
||||
|
||||
use RuntimeException;
|
||||
use function array_keys;
|
||||
use function file_exists;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function implode;
|
||||
use function is_bool;
|
||||
use function is_dir;
|
||||
use function json_decode;
|
||||
use function mkdir;
|
||||
use function sprintf;
|
||||
use function strtolower;
|
||||
|
||||
require_once __DIR__.'/parse-common.php';
|
||||
|
||||
if(!file_exists(FILE.'.json')){
|
||||
throw new RuntimeException('invalid benchmark report');
|
||||
}
|
||||
|
||||
$json = json_decode(file_get_contents(FILE.'.json'), true);
|
||||
$markdown = [];
|
||||
|
||||
// General information/overview
|
||||
|
||||
$env = $json['env'];
|
||||
$suite = $json['suite'];
|
||||
|
||||
$markdown['Readme'][] = "# Benchmark results\n";
|
||||
$markdown['Readme'][] = "## Environment\n";
|
||||
$markdown['Readme'][] = '| Name | Value |';
|
||||
$markdown['Readme'][] = '|------|-------|';
|
||||
$markdown['Readme'][] = sprintf('| date | %s %s |', $suite['date'], $suite['time']);
|
||||
$markdown['Readme'][] = sprintf('| environment | %s %s, %s |', $env['uname_os'], $env['uname_version'], $env['uname_machine']);
|
||||
$markdown['Readme'][] = sprintf('| tag | %s |', $suite['tag']);
|
||||
|
||||
|
||||
foreach(['php_version', 'php_ini', 'php_extensions', 'php_xdebug', 'opcache_extension_loaded', 'opcache_enabled'] as $field){
|
||||
|
||||
// prettify the boolean values
|
||||
if(is_bool($env[$field])){
|
||||
$env[$field] = ($env[$field]) ? '✓' : '✗';
|
||||
}
|
||||
|
||||
$markdown['Readme'][] = sprintf('| %s | %s |', $field, $env[$field]);
|
||||
}
|
||||
|
||||
// list indiviidual reports
|
||||
$markdown['Readme'][] = '';
|
||||
$markdown['Readme'][] = '## Reports';
|
||||
$markdown['Readme'][] = '';
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach(array_keys($json['benchmark']) as $benchmark){
|
||||
// add a file & header
|
||||
$markdown[$benchmark][] = sprintf("# %s\n", $benchmark);
|
||||
$markdown[$benchmark][] = sprintf("`%s`\n", $json['benchmark'][$benchmark]['class']);
|
||||
|
||||
foreach(array_keys($json['benchmark'][$benchmark]['subjects']) as $subject){
|
||||
// list item
|
||||
$list[$benchmark][] = $subject;
|
||||
|
||||
$subj = $json['benchmark'][$benchmark]['subjects'][$subject];
|
||||
|
||||
$markdown[$benchmark][] = sprintf("## %s\n", $subject);
|
||||
$markdown[$benchmark][] = sprintf("**Revs: %s, Iterations: %s**\n", $subj['revs'], $subj['iterations']);
|
||||
$markdown[$benchmark][] = parseVariants($subj['variants']);
|
||||
$markdown[$benchmark][] = '';
|
||||
}
|
||||
|
||||
$markdown[$benchmark][] = '[back to overview](./Benchmark.md)';
|
||||
}
|
||||
|
||||
// create overview list
|
||||
foreach($list as $benchmark => $subjects){
|
||||
// list item
|
||||
$markdown['Readme'][] = sprintf('- [%1$s](./%1$s.md)', $benchmark);
|
||||
|
||||
foreach($subjects as $subject){
|
||||
// list sub-item
|
||||
$markdown['Readme'][] = sprintf(' - [%2$s](./%1$s.md#%3$s)', $benchmark, $subject, strtolower($subject));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(!is_dir(BUILDDIR.'/markdown/')){
|
||||
mkdir(BUILDDIR.'/markdown/');
|
||||
}
|
||||
|
||||
foreach($markdown as $file => $content){
|
||||
file_put_contents(BUILDDIR.'/markdown/'.$file.'.md', implode("\n", $content));
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* @created 27.04.2024
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2024 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace chillerlan\QRCodeBenchmark;
|
||||
|
||||
use function array_column;
|
||||
use function array_key_first;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function array_sum;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function intdiv;
|
||||
use function rsort;
|
||||
use function sprintf;
|
||||
use function str_repeat;
|
||||
use const SORT_NUMERIC;
|
||||
|
||||
const BUILDDIR = __DIR__.'/../.build/phpbench';
|
||||
const FILE = BUILDDIR.'/benchmark'; // without extension
|
||||
|
||||
function parseVariants(array $variants):string{
|
||||
$data = [];
|
||||
|
||||
foreach($variants as $variant){
|
||||
[$version, $ecc, $mode] = explode(',', $variant['name']);
|
||||
|
||||
$data[(int)$version][$mode][$ecc] = parseVariantResults($variant['results']);
|
||||
}
|
||||
|
||||
$v = $data[array_key_first($data)];
|
||||
$modeKeys = array_keys($v);
|
||||
$eccKeys = array_keys($v[$modeKeys[0]]);
|
||||
|
||||
$modeHeaders = array_map(fn($mode) => sprintf('<th colspan="4">%s</th>', $mode), $modeKeys);
|
||||
$eccHeaders = array_map(fn($ecc) => sprintf('<th>%s</th>', $ecc), $eccKeys);
|
||||
|
||||
$table = ['<table><thead>'];
|
||||
$table[] = sprintf('<tr><th></th>%s</tr>', implode('', $modeHeaders));
|
||||
$table[] = sprintf('<tr><th>Version</th>%s</tr>', str_repeat(implode('', $eccHeaders), count($modeKeys)));
|
||||
$table[] = '</thead><tbody>';
|
||||
|
||||
foreach($data as $version => $modes){
|
||||
|
||||
$results = [];
|
||||
|
||||
foreach($modes as $eccLevels){
|
||||
foreach($eccLevels as [$time_avg, $mem_peak]){
|
||||
$results[] = sprintf('<td>%01.3f</td>', $time_avg);
|
||||
}
|
||||
}
|
||||
|
||||
$table[] = sprintf('<tr><td>%s</td>%s</tr>', $version, implode('', $results));
|
||||
|
||||
}
|
||||
|
||||
$table[] = '</tbody></table>';
|
||||
|
||||
return implode("\n", $table);
|
||||
}
|
||||
|
||||
function parseVariantResults(array $results):array{
|
||||
$iterations = count($results);
|
||||
$mem_peak = array_column($results, 'mem_peak');
|
||||
|
||||
rsort($mem_peak, SORT_NUMERIC);
|
||||
|
||||
return [
|
||||
(array_sum(array_column($results, 'time_avg')) / $iterations / 1000),
|
||||
intdiv($mem_peak[0], 1024),
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
/**
|
||||
* Parses the CSV result into more handy JSON
|
||||
*
|
||||
* @created 26.04.2024
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2024 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace chillerlan\QRCodeBenchmark;
|
||||
|
||||
use function array_combine;
|
||||
use function array_map;
|
||||
use function array_shift;
|
||||
use function explode;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function floatval;
|
||||
use function intval;
|
||||
use function json_encode;
|
||||
use function str_replace;
|
||||
use function str_starts_with;
|
||||
use function trim;
|
||||
use const JSON_PRESERVE_ZERO_FRACTION;
|
||||
use const JSON_PRETTY_PRINT;
|
||||
|
||||
require_once __DIR__.'/parse-common.php';
|
||||
|
||||
const SEPARATOR = '|';
|
||||
|
||||
function parseLine(string $line):array{
|
||||
return array_map(fn(string $str):string => trim($str, "\ \n\r\t\v\0\""), explode(SEPARATOR, $line));
|
||||
}
|
||||
|
||||
$csv = explode("\n", trim(file_get_contents(FILE.'.csv')));
|
||||
$header = parseLine(array_shift($csv));
|
||||
$parsed = array_map(fn(string $line):array => array_combine($header, parseLine($line)), $csv);
|
||||
$json = ['env' => [], 'suite' => [], 'benchmark' => []];
|
||||
|
||||
foreach($parsed as $i => $result){
|
||||
|
||||
// booleans
|
||||
foreach(['has_baseline', 'env_php_xdebug', 'env_opcache_extension_loaded', 'env_opcache_enabled'] as $bool){
|
||||
$result[$bool] = (bool)$result[$bool];
|
||||
}
|
||||
|
||||
// integers
|
||||
foreach(['variant_index', 'variant_revs', 'variant_iterations', 'iteration_index', 'result_time_net', 'result_time_revs', 'result_mem_peak', 'result_mem_real', 'result_mem_final'] as $int){
|
||||
$result[$int] = intval($result[$int]);
|
||||
}
|
||||
|
||||
// floats
|
||||
foreach(['env_sampler_nothing', 'env_sampler_md5', 'env_sampler_file_rw', 'result_time_avg', 'result_comp_z_value', 'result_comp_deviation'] as $float){
|
||||
$result[$float] = floatval($result[$float]);
|
||||
}
|
||||
|
||||
// arrays
|
||||
foreach(['subject_groups', 'variant_params'] as $array){
|
||||
$val = trim($result[$array], '"[]');
|
||||
|
||||
if($val === ''){
|
||||
$result[$array] = [];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$val = array_map('trim', explode(',', $val));
|
||||
|
||||
$result[$array] = $val;
|
||||
}
|
||||
|
||||
// rename some things to avoid bloat
|
||||
$result['subject_revs'] = $result['variant_revs'];
|
||||
$result['subject_iterations'] = $result['variant_iterations'];
|
||||
$result['result_index'] = $result['iteration_index'];
|
||||
|
||||
unset($result['variant_revs'], $result['variant_iterations'], $result['iteration_index'], $result['result_time_revs']);
|
||||
|
||||
// add the class name
|
||||
$json['benchmark'][$result['benchmark_name']]['class'] = $result['benchmark_class'];
|
||||
|
||||
foreach($result as $k => $v){
|
||||
|
||||
// the environment info is only needed once
|
||||
if(str_starts_with($k, 'env_')){
|
||||
|
||||
if($i === 0){
|
||||
$json['env'][str_replace('env_', '', $k)] = $v;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// suite info is needed only once
|
||||
if(str_starts_with($k, 'suite_')){
|
||||
|
||||
if($i === 0){
|
||||
$json['suite'][str_replace('suite_', '', $k)] = $v;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// test subject info once per test subject
|
||||
if(str_starts_with($k, 'subject_')){
|
||||
|
||||
// skip the name as it is the key
|
||||
if($k === 'subject_name'){
|
||||
continue;
|
||||
}
|
||||
|
||||
$json['benchmark'][$result['benchmark_name']]['subjects'][$result['subject_name']][str_replace('subject_', '', $k)] = $v;
|
||||
}
|
||||
|
||||
// add variants
|
||||
if(str_starts_with($k, 'variant_')){
|
||||
$json['benchmark'][$result['benchmark_name']]['subjects'][$result['subject_name']]['variants'][$result['variant_index']][str_replace('variant_', '', $k)] = $v;
|
||||
}
|
||||
|
||||
// add benchmark results per variant
|
||||
if(str_starts_with($k, 'result_')){
|
||||
$json['benchmark'][$result['benchmark_name']]['subjects'][$result['subject_name']]['variants'][$result['variant_index']]['results'][$result['result_index']][str_replace('result_', '', $k)] = $v;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
file_put_contents(FILE.'.json', json_encode($json, (JSON_PRESERVE_ZERO_FRACTION|JSON_PRETTY_PRINT)));
|
||||
@@ -52,6 +52,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"chillerlan/php-authenticator": "^5.1",
|
||||
"phpbench/phpbench": "^1.2.15",
|
||||
"phan/phan": "^5.4",
|
||||
"phpunit/phpunit": "^11.0",
|
||||
"phpmd/phpmd": "^2.15",
|
||||
@@ -70,10 +71,15 @@
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"chillerlan\\QRCodeBenchmark\\": "benchmark/",
|
||||
"chillerlan\\QRCodeTest\\": "tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"phpbench":[
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"@php vendor/bin/phpbench run"
|
||||
],
|
||||
"phpunit": "@php vendor/bin/phpunit",
|
||||
"phan": "@php vendor/bin/phan"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "vendor/phpbench/phpbench/phpbench.schema.json",
|
||||
"core.extensions": [
|
||||
"PhpBench\\Extensions\\XDebug\\XDebugExtension"
|
||||
],
|
||||
"xdebug.command_handler_output_dir": ".build/phpbench/xdebug-profile",
|
||||
"runner.bootstrap": "vendor/autoload.php",
|
||||
"runner.path": "benchmark",
|
||||
"runner.file_pattern": "*Benchmark.php",
|
||||
"console.ansi": true,
|
||||
"report.outputs": {
|
||||
"csv": {
|
||||
"extends": "delimited",
|
||||
"delimiter": "|",
|
||||
"file": ".build/phpbench/benchmark.csv",
|
||||
"header": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
<?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));
|
||||
@@ -1,80 +0,0 @@
|
||||
<?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 $FQN){
|
||||
$name = str_replace('chillerlan\\QRCode\\Output\\', '', $FQN);
|
||||
|
||||
yield sprintf('version %2s: %-14s', $version, $name) => [
|
||||
$version->getVersionNumber(),
|
||||
$FQN,
|
||||
substr($str, 0, self::getMaxLengthForMode(Mode::BYTE, $version, $eccLevel)),
|
||||
$name,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$test = new PerformanceTest(100);
|
||||
$json = [];
|
||||
|
||||
foreach($generator->dataProvider() as $key => [$version, $FQN, $data, $name]){
|
||||
|
||||
$options = new QROptions([
|
||||
'version' => $version,
|
||||
'outputInterface' => $FQN,
|
||||
'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));
|
||||
@@ -1,84 +0,0 @@
|
||||
<?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\QRStringJSON;
|
||||
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([
|
||||
'outputInterface' => QRStringJSON::class,
|
||||
'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));
|
||||
@@ -1,92 +0,0 @@
|
||||
<?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));
|
||||
Reference in New Issue
Block a user