pyrsmk / streetfight
A simple benchmarking tool
Requires
- php: >=7.2.0
- pyrsmk/funktions: ^0
- pyrsmk/illuminator: ^3
Requires (Dev)
- pyrsmk/minisuite: ^5.0
This package is auto-updated.
Last update: 2024-12-11 16:50:59 UTC
README
StreetFight is a benchmarking tool aiming to quickly know what code have better performance against another one. It is not intended to be an exhaustive profiling library and probably won't grow any further.
Note that StreetFight is following the Elegant Objects principle, and uses some functional programming internally with the help of Funktions.
Install
StreetFight requires PHP 7.2.
composer require pyrsmk/streetfight
Example
Here's the common way to execute a benchmark and retrieve a report directly from it. This example compares the performance of pre-increment and post-increment instructions.
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 ] */
Match objects
There are 3 types of matches:
StreetFight\Match\Match(int $rounds, RoundInterface $round)
: it will take a number of rounds to run, and aStreetFight\Round\Round
objectStreetFight\Match\TimedMatch(int $time, RoundInterface $round)
: it will take a minimum time in milliseconds while the benchmark will run, and aStreetFight\Round\Round
objectStreetFight\Match\AutoTimedMatch(RoundInterface $round)
: contrary toTimedMatch
, it will automatically compute the maximum time for the benchmark to run, and only accepts aStreetFight\Round\Round
object as parameter
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 objects
There are several types of Report
objects:
StreetFight\Report\Report(MatchInterface $match)
: the mainReport
object, can only take aMatch
object as parameter; the results are returned as raw secondsStreetFight\Report\RoundedSecondsReport
: a decorator for the mainReport
object; the results are returned as seconds rounded to 2 decimal digitsStreetFight\Report\MillisecondsReport
: a decorator for the mainReport
object; the results are returned as millisecondsStreetFight\Report\MicrosecondsReport
: a decorator for the mainReport
object; the results are returned as microsecondsStreetFight\Report\PercentageReport
: a decorator for the mainReport
object; the results are returned in percentageStreetFight\Report\AscSortedReport
: a decorator that sorts a report in ascending directionStreetFight\Report\DescSortedReport
: a decorator that sorts a report in descending direction
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++; }) ) ) ) ) ) )
Set BEFORE and AFTER hooks
If you need to run some specific routines, you can set them in the Round
object:
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'); }) )
Passing some data to tasks
As you can see from the above example, the same data is used across all tasks. Furthermore, we could need to generate random data at each iteration. But since mutability makes it really difficult for StreetFight to keep track on the data (and passing it into objects complicates the API), it has been decided to define it outside of StreetFight itself in a procedural way. So, here's how you would use arbitrary data, based on the previous example:
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']); }) )
Side note
Depending on what code you're benchmarking, the execution time can exceed the max_execution_time
directive of PHP. Just set set_time_limit(0)
and you'll be all good.
License
MIT.