pyrsmk/streetfight

一个简单的基准测试工具

6.0.4 2019-08-21 15:03 UTC

README

StreetFight是一个基准测试工具,旨在快速了解某段代码与另一段代码相比性能如何。它并不打算成为一个详尽的性能分析库,可能不会再进一步发展。

注意,StreetFight遵循优雅对象原则,并在内部使用Funktions的帮助下进行一些函数式编程。

安装

StreetFight需要PHP 7.2。

composer require pyrsmk/streetfight

示例

以下是执行基准测试并直接从其中检索报告的常见方式。此示例比较了预增量操作和后增量操作的性能。

use StreetFight\Challenger\Challenger;
use StreetFight\Challenger\ChallengerList;
use StreetFight\Round\Round;
use StreetFight\Match\AutoTimedMatch;
use StreetFight\Report\Report;
use StreetFight\Report\PercentageReport;
use StreetFight\Report\DescSortedReport;

$report =
    // Sort the report in descending direction
    new DescSortedReport(
        // Convert the seconds report to percentage
        new PercentageReport(
            // Create a report to process match results
            new Report(
                // Create a match to process the benchmark for a certain time
                // (here, AutoTimedMatch will compute automatically the time of the match)
                // (see below for types of match)
                new AutoTimedMatch(
                    // A typical round...
                    new Round(
                        // ...with a challenger list
                        new ChallengerList(
                            new Challenger('Pre-increment', function () {
                                $i = 0;
                                ++$i;
                            }),
                            new Challenger('Post-increment', function () {
                                $i = 0;
                                $i++;
                            })
                        )
                    )
                )
            )
        )
    );

var_dump($report->asPercentages());
/*
    [
        'Post-increment' => 100,
        'Pre-increment' => 98.84
    ]
*/

匹配对象

有三种类型的匹配

  • StreetFight\Match\Match(int $rounds, RoundInterface $round):它将运行一定数量的回合,并需要一个StreetFight\Round\Round对象
  • StreetFight\Match\TimedMatch(int $time, RoundInterface $round):它将在基准测试运行期间至少运行指定毫秒的时间,并需要一个StreetFight\Round\Round对象
  • StreetFight\Match\AutoTimedMatch(RoundInterface $round):与TimedMatch相反,它将自动计算基准测试运行的最大时间,并仅接受一个StreetFight\Round\Round对象作为参数
use StreetFight\Challenger\Challenger;
use StreetFight\Challenger\ChallengerList;
use StreetFight\Round\Round;
use StreetFight\Match\Match;

new Match(
    100, // Will iterate 100 times
    new Round(
        new ChallengerList(
            new Challenger('Pre-increment', function () {
                $i = 0;
                ++$i;
            }),
            new Challenger('Post-increment', function () {
                $i = 0;
                $i++;
            })
        )
    )
)
use StreetFight\Challenger\Challenger;
use StreetFight\Challenger\ChallengerList;
use StreetFight\Round\Round;
use StreetFight\Match\TimedMatch;

new TimedMatch(
    5000, // Will run for 5 seconds at least
    new Round(
        new ChallengerList(
            new Challenger('Pre-increment', function () {
                $i = 0;
                ++$i;
            }),
            new Challenger('Post-increment', function () {
                $i = 0;
                $i++;
            })
        )
    )
)
use StreetFight\Challenger\Challenger;
use StreetFight\Challenger\ChallengerList;
use StreetFight\Round\Round;
use StreetFight\Match\AutoTimedMatch;

// The recommended and simplest way to run the benchmark
new AutoTimedMatch(
    new Round(
        new ChallengerList(
            new Challenger('Pre-increment', function () {
                $i = 0;
                ++$i;
            }),
            new Challenger('Post-increment', function () {
                $i = 0;
                $i++;
            })
        )
    )
)

报告对象

有几种类型的Report对象

  • StreetFight\Report\Report(MatchInterface $match):主要的Report对象,只能接受一个Match对象作为参数;结果以原始秒为单位返回
  • StreetFight\Report\RoundedSecondsReport:主要Report对象的装饰器;结果以秒为单位四舍五入到两位小数
  • StreetFight\Report\MillisecondsReport:主要Report对象的装饰器;结果以毫秒为单位返回
  • StreetFight\Report\MicrosecondsReport:主要Report对象的装饰器;结果以微秒为单位返回
  • StreetFight\Report\PercentageReport:主要Report对象的装饰器;结果以百分比形式返回
  • StreetFight\Report\AscSortedReport:一个装饰器,将报告按升序排序
  • StreetFight\Report\DescSortedReport:一个装饰器,将报告按降序排序
use StreetFight\Challenger\Challenger;
use StreetFight\Challenger\ChallengerList;
use StreetFight\Round\Round;
use StreetFight\Match\AutoTimedMatch;
use StreetFight\Report\Report;
use StreetFight\Report\MicrosecondsReport;
use StreetFight\Report\AscSortedReport;

// Sort the report
new AscSortedReport(
    // Format the report results in microseconds
    new MicrosecondsReport(
        // The main Report object
        new Report(
            new AutoTimedMatch(
                new Round(
                    new ChallengerList(
                        new Challenger('Pre-increment', function () {
                            $i = 0;
                            ++$i;
                        }),
                        new Challenger('Post-increment', function () {
                            $i = 0;
                            $i++;
                        })
                    )
                )
            )
        )
    )
)

设置BEFORE和AFTER钩子

如果您需要运行一些特定的例程,您可以在Round对象中设置它们

use StreetFight\Challenger\Challenger;
use StreetFight\Challenger\ChallengerList;
use StreetFight\Round\Round;
use StreetFight\Hook\Hook;

new Round(
    new ChallengerList(
        new Challenger('file_put_contents (overwrite)', function () {
            file_put_contents('foo.txt', 'bar');
        }),
        new Challenger('fwrite (overwrite)', function () {
            $f = fopen('foo.txt', 'w');
            fwrite($f, 'bar');
            fclose($f);
        }),
        new Challenger('file_put_contents (append)', function () {
            file_put_contents('foo.txt', 'bar', FILE_APPEND);
        }),
        new Challenger('fwrite (append)', function () {
            $f = fopen('foo.txt', 'a');
            fwrite($f, 'bar');
            fclose($f);
        }),
    ),
    // Set a hook that will be run BEFORE each task of each iteration
    new Hook(function () {
        touch('foo.txt');
    }),
    // Set a hook that will be run AFTER each task of each iteration
    new Hook(function () {
        unlink('foo.txt');
    })
)

向任务传递一些数据

如上例所示,相同的数据被用于所有任务。此外,我们可能需要在每个迭代中生成随机数据。但是,由于可变性使得StreetFight难以跟踪数据(并且将数据传递到对象中会复杂化API),因此决定在StreetFight本身之外以过程化方式定义它。以下是如何使用任意数据的示例,基于之前的示例

use StreetFight\Challenger\Challenger;
use StreetFight\Challenger\ChallengerList;
use StreetFight\Round\Round;
use StreetFight\Hook\Hook;

$data = [
    'filename' => 'foo.txt',
    'content' => 'bar'
];

new Round(
    new ChallengerList(
        new Challenger('file_put_contents (overwrite)', function () use ($data) {
            file_put_contents($data['filename'], $data['content']);
        }),
        new Challenger('fwrite (overwrite)', function () use ($data) {
            $f = fopen($data['filename'], 'w');
            fwrite($f, $data['content']);
            fclose($f);
        }),
        new Challenger('file_put_contents (append)', function () use ($data) {
            file_put_contents($data['filename'], $data['content'], FILE_APPEND);
        }),
        new Challenger('fwrite (append)', function () use ($data) {
            $f = fopen($data['filename'], 'a');
            fwrite($f, $data['content']);
            fclose($f);
        }),
    ),
    new Hook(function () {
        touch($data['filename']);
    }),
    new Hook(function () {
        unlink($data['filename']);
    })
)

备注

根据您正在基准测试的代码,执行时间可能会超过PHP的max_execution_time指令。只需设置set_time_limit(0)即可。

许可

MIT.