📜 ⬆️ ⬇️

PHP function speed analysis tool

Recently, I paid attention to the materials on performance and speed measurements of PHP functions. After analyzing a number of materials, the following conclusion was made. There are quite a few comparisons, but all measurements are carried out with different input conditions, the output of test results is different for each solution, not to mention if there is a desire to test the tests in your environment, you will have to copy and paste pieces of code.

Therefore, the idea arose to write our own simple mechanism for testing the speed of various operations. The idea seemed interesting, so the start was made!

Goals


We should start by setting goals for the future tool. I wanted to see the project as a universal tool for testing the speed of anything. But after thinking over the details and mistakes of the past , it was decided to start small.

So, it was necessary to get a tool that:
')

Measurement mechanism


To measure the execution time of the function, it was decided to use a similar, method

protected function getTime($time = false) { return $time === false? microtime(true) : microtime(true) - $time; } 

The principle of operation is simple, if a call occurs without parameters, then we return the current state of time, if the call occurs with the time parameter, then the difference between the current time and the transmitted time is returned.

But where there is the calculation of time, there is also the amount of memory consumed, so later a similar method of measuring consumed memory was added.

 protected function getMemory($memory = false) { return $memory === false? memory_get_usage() : memory_get_usage() - $memory; } 

The mechanism is similar to the mechanics of time measurement, only in this case the value of the allocated memory is transferred to the script.

As you know, some functions behave differently depending on the input data set, therefore, in each test, an array of input data is defined for the testing functions. To obtain the most accurate results, functions are tested on each set several times. Among other things, testing is carried out in different sequences.

During the experiments, it was noted that the amount of free memory before each test is different, although for most tests it was insignificant, but for the order I would like to have conditions that are the same for all tests or close to those. Therefore, it was decided to store the results in the local SQLite repository, and before starting the tests, do one verification test for filling variables with data.

In order to be able to create your own options for presenting results and your own test options, 2 abstraction models have been created. Model test (Test), contains all the information about the test, including test functions. The DataViwer model contains methods for converting test results to a readable form. For convenient output of views, the Twig template engine was used and the bootstrap style library was connected, and a HighCharts viewer was also created.

As a result, the general mechanism of such work. The functions necessary for comparison are taken and run on execution with different data sets and in different sequences. When this occurs, the time of each execution is measured and the result is recorded in the repository. The test model (Test) is responsible for this stage. After all measurements, the results are transferred to the data view (DataViwer), where information is processed and output.

Implementation of testing


Test class


A simple example of the implementation of the test class, on the example of testing the speed of the operation before the increment and post increment.

Test class example
 class IncPrefVsPos extends TestAbstract { public $name = 'Speedy ++i vs i++'; public $valueTest = [100, 1000, 2000, 3000]; public $qntTest = 5; public $viewers = [TestCore::VIEWER_TLIST, TestCore::VIEWER_TGROUP, TestCore::VIEWER_TAVG, TestCore::VIEWER_GBUBLE]; public $functions = ['postIncrement' => 'testPost', 'prefIncrement' => 'testPref']; protected $strategy = [['testPost', 'testPref'], ['testPref', 'testPost']]; public function testPref($size) { $testCounter = 0; for($i=0;$i<$size;$i++) { ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; } } public function testPost($size) { $testCounter = 0; for($i=0;$i<$size;$i++) { $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; } } } 

The class must inherit the abstract class TestAbstract , which contains the basic mechanics of working with data streams.

$ name - sets the name of the test that can be used when outputting to the DataViewer.

$ valueTest is the sample size of the test, an array of values, with each of which a function will be executed for testing. Thus, each tested function must necessarily take one parameter. It will be a number, string or array is not important, it all depends on the specific case. In our example, there are enough numbers that will indicate the volume of operations performed with an increment.

$ qntTest - tells how many times each sample size will be tested.

$ viewers - an array of data views that will be generated when rendering a report. In fact, this is a set of fully qualified class names that inherit the abstract ViewrAbstract class. Pre-prepared viewers are rendered in TestCore class constants.

$ functions - an array of names of functions that will be used in testing. The keys of the array are the names that will be displayed in the results.

$ strategy is an array of testing strategies; each strategy must be represented by an array with a sequence of function names. In the example, 2 strategies are indicated - direct sequence and in reverse.

The functions themselves, which, as mentioned above, should take one value. In our example, we consider this the number of incremental series operations performed.

As a result, we get a series of tests where each testing strategy will be tested 5 times with each sample size ([100, 1000, 2000, 3000]).

Presentation class


Now let's take a closer look at a simple data presentation class.

View class example
 class TableList extends ViewerAbstract { public $view = 'tableList.php'; public function generateData($data){ return $data; } public function run($data) { $data = $this->generateData($data); return App::render('/viewer/'.$this->view, compact('data')); } public static function model($class = __CLASS__) { return parent::model($class); } } 

The class implements the abstract class ViewerAbstract .

$ view - contains the name of the view, which by default should be in the folder / views / viewers

function generateData ($ data) is a method for processing an array of test results. The result will be transferred to the default data parameter in the specified view.

function run ($ data) is an optional method for the implementation, but if it is necessary to change the path to the data representation, then it should be redefined.

public static function model ($ class = __CLASS__) - method for supporting static method calls

Use cases


To use the testing functionality, 2 main methods were created.

Test run method


 function test($test, $params = [], $onlyData = false) 

$ test - the full name of the test being run. They can be used as their test variants or in advance prepared.

$ params - an array of parameters to run the test. With this parameter you can point test parameters.

$ onlyData - the parameter responsible for displaying the result set or a rendered view.

Function comparison method


Method of comparing user functions without creating additional classes

 function compare($func = [], $params = [], $onlyData = false) 

$ func is an array of anonymous functions, where the keys of the array are the names of the functions in the test results.

$ params - an array of test parameters, similar to the parameters of the test method.

$ onlyData is also similar to the test method parameter, and is responsible for the output option.

Work examples


The easiest and fastest use case is to use a prepared test. All prepared tests are rendered into constants of the Speedy class.

 print Speedy::test(Speedy::PHP_SOF_VS_COUNT); 

To compare user-defined functions, you must create anonymous functions and call the Speedy :: compare method

Function comparison example
 $pref = function($size) { $testCounter = 0; for($i=0;$i<$size;$i++) { ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; } }; $post = function($size) { $testCounter = 0; for($i=0;$i<$size;$i++) { $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; } }; print \speedy\Speedy::compare(['pref' => $pref, 'post' => $post]); 

Test results


Test results for the moment can be displayed by 4 views.

VIEWER_TLIST


Tabular listing of all time measurements taken. In essence, this is the output of all records from the repository, without any transformations. The data set contains information about the name of the function being tested, the elapsed time, the sample size, the used memory size, the batch number in which the test and comment were performed. The comment indicates within which strategy the test of this function was performed.

Example of displaying the results in a list in tabular form
The result of testing increment operators.
nametimesizememorypartcomment
postInc0.000650882720947100482postInc-prefInc
prefInc0.000411987304688100482postInc-prefInc
prefInc0.000406980514526100483prefInc-postInc
postInc0.000549077987671100483prefInc-postInc
postInc0.00033092498779310048fivepostInc-prefInc
prefInc0.00028705596923810048fivepostInc-prefInc
prefInc0.00043797492981100486prefInc-postInc
postInc0.000365018844604100486prefInc-postInc
postInc0.00029516220092810048eightpostInc-prefInc
prefInc0.00037312507629410048eightpostInc-prefInc
prefInc0.000263929367065100489prefInc-postInc
postInc0.000449895858765100489prefInc-postInc
postInc0.0003051757812510048elevenpostInc-prefInc
prefInc0.00024795532226610048elevenpostInc-prefInc
prefInc0.0002441406251004812prefInc-postInc
postInc0.0002651214599611004812prefInc-postInc
postInc0.0002670288085941004814postInc-prefInc
prefInc0.0002450942993161004814postInc-prefInc
prefInc0.0002851486206051004815prefInc-postInc
postInc0.0002739429473881004815prefInc-postInc
postInc0.0027320384979210004817postInc-prefInc
prefInc0.0024020671844510004817postInc-prefInc
prefInc0.0027489662170410004818prefInc-postInc
postInc0.0025980472564710004818prefInc-postInc
postInc0.0039181709289610004820postInc-prefInc
prefInc0.0030360221862810004820postInc-prefInc
prefInc0.0022909641265910004821prefInc-postInc
postInc0.0028169155120810004821prefInc-postInc
postInc0.0027310848236110004823postInc-prefInc
prefInc0.0022101402282710004823postInc-prefInc
prefInc0.0026681423187310004824prefInc-postInc
postInc0.0030710697174110004824prefInc-postInc
postInc0.0028309822082510004826postInc-prefInc
prefInc0.0023999214172410004826postInc-prefInc
prefInc0.0024621486663810004827prefInc-postInc
postInc0.0027370452880910004827prefInc-postInc
postInc0.0028328895568810004829postInc-prefInc
prefInc0.0022921562194810004829postInc-prefInc
prefInc0.00220608711243100048thirtyprefInc-postInc
postInc0.0028657913208100048thirtyprefInc-postInc
postInc0.0055701732635520004832postInc-prefInc
prefInc0.004884004592920004832postInc-prefInc
prefInc0.0044901371002220004833prefInc-postInc
postInc0.006479978561420004833prefInc-postInc
postInc0.0054359436035220004835postInc-prefInc
prefInc0.0050988197326720004835postInc-prefInc
prefInc0.0048329830169720004836prefInc-postInc
postInc0.0055599212646520004836prefInc-postInc
postInc0.0051610469818120004838postInc-prefInc
prefInc0.0051259994506820004838postInc-prefInc
prefInc0.0048439502716120004839prefInc-postInc
postInc0.0053050518035920004839prefInc-postInc
postInc0.0050969123840320004841postInc-prefInc
prefInc0.0052509307861320004841postInc-prefInc
prefInc0.0044741630554220004842prefInc-postInc
postInc0.0053658485412620004842prefInc-postInc
postInc0.005466938018820004844postInc-prefInc
prefInc0.0046818256378220004844postInc-prefInc
prefInc0.0051250457763720004845prefInc-postInc
postInc0.0054569244384820004845prefInc-postInc
postInc0.0078241825103830004847postInc-prefInc
prefInc0.0072638988494930004847postInc-prefInc
prefInc0.0067479610443130004848prefInc-postInc
postInc0.00748395919830004848prefInc-postInc
postInc0.0078129768371630004850postInc-prefInc
prefInc0.006958007812530004850postInc-prefInc
prefInc0.0071139335632330004851prefInc-postInc
postInc0.007280826568630004851prefInc-postInc
postInc0.0079011917114330004853postInc-prefInc
prefInc0.0066299438476630004853postInc-prefInc
prefInc0.0082559585571330004854prefInc-postInc
postInc0.0073909759521530004854prefInc-postInc
postInc0.0081110000610430004856postInc-prefInc
prefInc0.0071299076080330004856postInc-prefInc
prefInc0.0069839954376230004857prefInc-postInc
postInc0.0075821876525930004857prefInc-postInc
postInc0.0079531669616730004859postInc-prefInc
prefInc0.0072569847106930004859postInc-prefInc
prefInc0.0068409442901630004860prefInc-postInc
postInc0.0077819824218830004860prefInc-postInc

VIEWER_TGROUP


Presentation in tabular form in the form of grouped data on the test batch (by the pass number of the strategy), i.e. in one line will be the results of testing functions that were carried out in the framework of a single strategy and in one test run.

The columns of the table will display data on the sample size, runtime, percentage of speed from the worst result, memory wasted, comment, and the name of the function that became the winner in time among the current pass. Separately, I would like to clarify the percentage column. This value is calculated as a percentage, by how much time the function outpaced the execution of the slowest function. If the value is not set, then this function is an outsider in the pass according to the execution time.

Example of output of grouped results in tabular form
The result of testing increment operators.
sizepostIncprefInccommenttime win
time%memorytime%memory
1000.000650882720947480.00041198730468836.748postInc-prefIncprefInc
1000.000549077987671480.00040698051452625.8848prefInc-postIncprefInc
1000.000330924987793480.00028705596923813.2648postInc-prefIncprefInc
1000.00036501884460416.66480.0004379749298148prefInc-postIncpostInc
1000.00029516220092820.89480.00037312507629448postInc-prefIncpostInc
1000.000449895858765480.00026392936706541.3448prefInc-postIncprefInc
1000.00030517578125480.00024795532226618.7548postInc-prefIncprefInc
1000.000265121459961480.0002441406257.9148prefInc-postIncprefInc
1000.000267028808594480.0002450942993168.2148postInc-prefIncprefInc
1000.0002739429473883.93480.00028514862060548prefInc-postIncpostInc
10000.00273203849792480.0024020671844512.0848postInc-prefIncprefInc
10000.002598047256475.49480.0027489662170448prefInc-postIncpostInc
10000.00391817092896480.0030360221862822.5148postInc-prefIncprefInc
10000.00281691551208480.0022909641265918.6748prefInc-postIncprefInc
10000.00273108482361480.0022101402282719.0748postInc-prefIncprefInc
10000.00307106971741480.0026681423187313.1248prefInc-postIncprefInc
10000.00283098220825480.0023999214172415.2348postInc-prefIncprefInc
10000.00273704528809480.0024621486663810.0448prefInc-postIncprefInc
10000.00283288955688480.0022921562194819.0948postInc-prefIncprefInc
10000.0028657913208480.0022060871124323.0248prefInc-postIncprefInc
20000.00557017326355480.004884004592912.3248postInc-prefIncprefInc
20000.0064799785614480.0044901371002230.7148prefInc-postIncprefInc
20000.00543594360352480.005098819732676.248postInc-prefIncprefInc
20000.00555992126465480.0048329830169713.0748prefInc-postIncprefInc
20000.00516104698181480.005125999450680.6848postInc-prefIncprefInc
20000.00530505180359480.004843950271618.6948prefInc-postIncprefInc
20000.005096912384032.93480.0052509307861348postInc-prefIncpostInc
20000.00536584854126480.0044741630554216.6248prefInc-postIncprefInc
20000.0054669380188480.0046818256378214.3648postInc-prefIncprefInc
20000.00545692443848480.005125045776376.0848prefInc-postIncprefInc
30000.00782418251038480.007263898849497.1648postInc-prefIncprefInc
30000.007483959198480.006747961044319.8348prefInc-postIncprefInc
30000.00781297683716480.006958007812510.9448postInc-prefIncprefInc
30000.0072808265686480.007113933563232.2948prefInc-postIncprefInc
30000.00790119171143480.0066299438476616.0948postInc-prefIncprefInc
30000.0073909759521510.48480.0082559585571348prefInc-postIncpostInc
30000.00811100006104480.0071299076080312.148postInc-prefIncprefInc
30000.00758218765259480.006983995437627.8948prefInc-postIncprefInc
30000.00795316696167480.007256984710698.7548postInc-prefIncprefInc
30000.00778198242188480.0068409442901612.0948prefInc-postIncprefInc


VIEWER_TAVG


Presentation in tabular form in the form of averaged indicators for the sample size, i.e. in one line will be the average indicators of testing functions for one sample.

The columns provide information on the number of victories of the function in the sample, the average time percentage of victories, the name of the winning function. Functions winner is determined by the number of wins in the sample.

An example of the output of averaged results in tabular form
The result of testing increment operators.
sizepostIncprefIncwinner
winns%winns%
100313.83721.72prefInc
1000one5.49916.98prefInc
2000one2.93912.08prefInc
3000one10.4899.68prefInc


VIEWER_GBUBLE

Representation in graphical form as a set of points, each of which corresponds to a time value and a sample size. This view is implemented using HighCharts charts.
An example of displaying the results in graphical form
The result of testing increment operators.
image

Results


To summarize it would be desirable that the goals have been achieved. But of course there is still work to do. The plans include working on exception handling, adding the ability to change the settings for a set of columns, as well as the ability to set your own column names, an increase in the number of tests. I would also like to add the ability to conveniently test the speed of working with databases.

The source code of the project can be viewed here.

PS : This project is being developed in the framework of personal interest in this topic and does not claim to be the best of its kind. I would be glad if someone likes and will be useful this work. Constructive criticism and advice are welcome.

Source: https://habr.com/ru/post/308654/


All Articles