├── .gitignore ├── .php_cs.cache ├── .php_cs.dist ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── phpunit ├── composer.json ├── examples ├── array_flip_vs_array_unique.php ├── array_key_exists_vs_isset.php ├── compare_placeholder_replacement.php ├── compare_sort_algorithms.php ├── list_vs_set_lookup.php └── single_quotes_vs_double_quotes.php ├── graph.gif ├── src └── mre │ ├── PHPench.php │ └── PHPench │ ├── Aggregator │ ├── AverageAggregator.php │ ├── MedianAggregator.php │ └── SimpleAggregator.php │ ├── AggregatorInterface.php │ ├── BenchmarkInterface.php │ ├── Output │ ├── CliOutput.php │ ├── GnuPlotOutput.php │ ├── OutputAbstract.php │ └── OutputInterface.php │ └── Util │ └── Math.php └── test └── .gitignore /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | .idea 4 | -------------------------------------------------------------------------------- /.php_cs.cache: -------------------------------------------------------------------------------- 1 | {"php":"7.3.10-1+ubuntu19.04.1+deb.sury.org+1","version":"2.15.1:v2.15.1#20064511ab796593a3990669eff5f5b535001f7c","indent":" ","lineEnding":"\n","rules":{"encoding":true,"full_opening_tag":true,"blank_line_after_namespace":true,"braces":true,"class_definition":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_constants":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_import_per_statement":true,"single_line_after_imports":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"visibility_required":true,"align_multiline_comment":{"comment_type":"phpdocs_like"},"array_syntax":{"syntax":"short"},"binary_operator_spaces":{"align_double_arrow":true,"align_equals":false},"blank_line_before_return":true,"declare_strict_types":true,"fully_qualified_strict_types":true,"new_with_braces":true,"no_blank_lines_after_phpdoc":true,"no_empty_comment":true,"no_empty_phpdoc":true,"no_empty_statement":true,"no_trailing_comma_in_list_call":true,"no_trailing_comma_in_singleline_array":true,"no_unneeded_curly_braces":true,"no_useless_else":true,"no_useless_return":true,"normalize_index_brace":true,"phpdoc_add_missing_param_annotation":true,"phpdoc_align":true,"phpdoc_annotation_without_dot":true,"phpdoc_indent":true,"phpdoc_inline_tag":true,"phpdoc_no_access":true,"phpdoc_no_empty_return":true,"phpdoc_no_package":true,"phpdoc_no_useless_inheritdoc":true,"phpdoc_order":true,"phpdoc_scalar":true,"phpdoc_single_line_var_spacing":true,"phpdoc_summary":true,"phpdoc_to_comment":true,"phpdoc_trim":true,"phpdoc_types":true,"return_type_declaration":true,"standardize_not_equals":true,"strict_comparison":true,"trailing_comma_in_multiline_array":true,"void_return":true,"yoda_style":true},"hashes":{"src\/mre\/PHPench\/Aggregator\/AverageAggregator.php":178832795,"src\/mre\/PHPench\/Aggregator\/SimpleAggregator.php":2550647887,"src\/mre\/PHPench\/Aggregator\/MedianAggregator.php":2613429501,"src\/mre\/PHPench\/Util\/Math.php":60120512,"src\/mre\/PHPench\/AggregatorInterface.php":1564311152,"src\/mre\/PHPench\/BenchmarkInterface.php":3901670017,"src\/mre\/PHPench\/Output\/OutputInterface.php":2833645009,"src\/mre\/PHPench\/Output\/CliOutput.php":1494048629,"src\/mre\/PHPench\/Output\/OutputAbstract.php":427433957,"src\/mre\/PHPench\/Output\/GnuPlotOutput.php":1920012737,"src\/mre\/PHPench.php":1014624947,"examples\/compare_placeholder_replacement.php":3193924593,"examples\/list_vs_set_lookup.php":3235878643,"examples\/compare_sort_algorithms.php":1844719808,"examples\/array_flip_vs_array_unique.php":3421709931,"examples\/single_quotes_vs_double_quotes.php":3205923498,"examples\/array_key_exists_vs_isset.php":2853927933}} -------------------------------------------------------------------------------- /.php_cs.dist: -------------------------------------------------------------------------------- 1 | setRiskyAllowed(true)->setRules( 8 | [ 9 | '@PSR1' => true, 10 | '@PSR2' => true, 11 | // Each line of multi-line DocComments must have an asterisk [PSR-5] and must be aligned with the first one. 12 | 'align_multiline_comment' => ['comment_type' => 'phpdocs_like'], 13 | // PHP arrays should be declared using the configured syntax. 14 | 'array_syntax' => ['syntax' => 'short'], 15 | // Binary operators should be surrounded by space as configured. 16 | 'binary_operator_spaces' => [ 17 | 'align_double_arrow' => true, 18 | 'align_equals' => false, 19 | ], 20 | // An empty line feed should precede a return statement. 21 | 'blank_line_before_return' => true, 22 | // Concatenation should be spaced according configuration. 23 | 'concat_space' => false, 24 | // Equal sign in declare statement should be surrounded by spaces or not following configuration. 25 | 'declare_equal_normalize' => false, 26 | // Force strict types declaration in all files. 27 | // Requires PHP >= 7.0. 28 | 'declare_strict_types' => true, 29 | // Transforms imported FQCN parameters and return types in function arguments to short version. 30 | 'fully_qualified_strict_types' => true, 31 | // All instances created with new keyword must be followed by braces. 32 | 'new_with_braces' => true, 33 | // There should not be blank lines between docblock and the documented element. 34 | 'no_blank_lines_after_phpdoc' => true, 35 | // There should not be any empty comments. 36 | 'no_empty_comment' => true, 37 | // There should not be empty PHPDoc blocks. 38 | 'no_empty_phpdoc' => true, 39 | // Remove useless semicolon statements. 40 | 'no_empty_statement' => true, 41 | // Remove trailing commas in list function calls. 42 | 'no_trailing_comma_in_list_call' => true, 43 | // PHP single-line arrays should not have trailing comma. 44 | 'no_trailing_comma_in_singleline_array' => true, 45 | // Removes unneeded curly braces that are superfluous and aren't part of a control structure's body. 46 | 'no_unneeded_curly_braces' => true, 47 | // There should not be useless `else` cases. 48 | 'no_useless_else' => true, 49 | // There should not be an empty `return` statement at the end of a function. 50 | 'no_useless_return' => true, 51 | // Array index should always be written by using square braces. 52 | 'normalize_index_brace' => true, 53 | // PHPDoc should contain `@param` for all params. 54 | 'phpdoc_add_missing_param_annotation' => true, 55 | // All items of the given phpdoc tags must be either left-aligned or (by default) aligned vertically. 56 | 'phpdoc_align' => true, 57 | // PHPDoc annotation descriptions should not be a sentence. 58 | 'phpdoc_annotation_without_dot' => true, 59 | // Docblocks should have the same indentation as the documented subject. 60 | 'phpdoc_indent' => true, 61 | // Fix PHPDoc inline tags, make `@inheritdoc` always inline. 62 | 'phpdoc_inline_tag' => true, 63 | // `@access` annotations should be omitted from PHPDoc. 64 | 'phpdoc_no_access' => true, 65 | // `@return void` and `@return null` annotations should be omitted from PHPDoc. 66 | 'phpdoc_no_empty_return' => true, 67 | // `@package` and `@subpackage` annotations should be omitted from PHPDoc. 68 | 'phpdoc_no_package' => true, 69 | // Classy that does not inherit must not have `@inheritdoc` tags. 70 | 'phpdoc_no_useless_inheritdoc' => true, 71 | // Annotations in PHPDoc should be ordered so that `@param` annotations come first, then `@throws` annotations, then `@return` annotations. 72 | 'phpdoc_order' => true, 73 | // Scalar types should always be written in the same form. 74 | // `int` not `integer`, `bool` not `boolean`, `float` not `real` or `double`. 75 | 'phpdoc_scalar' => true, 76 | // Single line `@var` PHPDoc should have proper spacing. 77 | 'phpdoc_single_line_var_spacing' => true, 78 | // PHPDoc summary should end in either a full stop, exclamation mark, or question mark. 79 | 'phpdoc_summary' => true, 80 | // Docblocks should only be used on structural elements. 81 | 'phpdoc_to_comment' => true, 82 | // PHPDoc should start and end with content, excluding the very first and last line of the docblocks. 83 | 'phpdoc_trim' => true, 84 | // The correct case must be used for standard PHP types in PHPDoc. 85 | 'phpdoc_types' => true, 86 | // There should be one or no space before colon, and one space after it in return type declarations, according to configuration. 87 | 'return_type_declaration' => true, 88 | // Replace all `<>` with `!=`. 89 | 'standardize_not_equals' => true, 90 | // Comparisons should be strict. 91 | 'strict_comparison' => true, 92 | // PHP multi-line arrays should have a trailing comma. 93 | 'trailing_comma_in_multiline_array' => true, 94 | // Add void return type to functions with missing or empty return statements, but priority is given to `@return` annotations. 95 | // Requires PHP >= 7.1. 96 | 'void_return' => true, 97 | // Write conditions in Yoda style (`true`), non-Yoda style (`false`) or ignore those conditions (`null`) based on configuration. 98 | 'yoda_style' => true, 99 | ] 100 | )->setFinder(PhpCsFixer\Finder::create()->exclude('vendor')->in(__DIR__)) 101 | ; -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.2 5 | 6 | before_script: 7 | - composer install 8 | 9 | script: ./bin/phpunit test/ 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2014 Matthias Endler 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHPench 2 | 3 | ![A pretty graph](graph.gif) 4 | 5 | PHPench creates a graphical output for a PHP benchmark. 6 | Plot the runtime of any function in realtime with GnuPlot and create an image 7 | out of the result. 8 | 9 | [![Build Status](https://travis-ci.org/mre/PHPench.svg)](https://travis-ci.org/mre/PHPench) 10 | 11 | ## Why is it useful? 12 | 13 | #### Algorithms are beautiful 14 | 15 | Sometimes the difference between two algorithms is hard to explain but easy to show. 16 | For instance, take two sorting algorithms which both have a best-case 17 | runtime of `O(n*log n)`. Depending on the input, one can be much faster than the 18 | other. This tools helps you see what's going on. 19 | 20 | #### Death to premature-optimizations 21 | 22 | Whenever people tell you that using single quotes instead of double quotes 23 | around strings is a performance improvement, it's time to debunk some myths. 24 | Most of the time such programmer folklore turns out to be misguided and can actually be pretty harmful. 25 | *"Premature emphasis on efficiency is a big mistake which may well be the source 26 | of most programming complexity and grief."* (Donald Knuth) 27 | Let's be professionals. Let's measure. 28 | 29 | ## Example 30 | 31 | Using PHPench feels a bit like writing a visual unit test. Check it out: 32 | 33 | ```PHP 34 | test = array(); 52 | for ($i=1; $i<$arrSize; $i++) { 53 | $this->test[$i]= $arrSize % $i; 54 | } 55 | 56 | return $this->test; 57 | } 58 | } 59 | 60 | class BenchmarkArrayFlip extends AbstractBenchmark 61 | { 62 | public function execute() { 63 | $test = array_flip(array_flip($this->test)); 64 | } 65 | } 66 | 67 | class BenchmarkArrayUnique extends AbstractBenchmark 68 | { 69 | public function execute() { 70 | $test = array_unique($this->test); 71 | } 72 | } 73 | 74 | // Create a new benchmark instance 75 | $phpench = new \mre\PHPench(new \mre\PHPench\Aggregator\MedianAggregator); 76 | 77 | // Use GnuPlot for output 78 | $oOutput = new \mre\PHPench\Output\GnuPlotOutput('test2.png', 1024, 768); 79 | 80 | // Alternatively, print the values to the terminal 81 | //$oOutput = new \mre\PHPench\Output\CliOutput(); 82 | 83 | $oOutput->setTitle('Compare array_flip and array_unique'); 84 | $phpench->setOutput($oOutput); 85 | 86 | // Add your test to the instance 87 | $phpench->addBenchmark(new BenchmarkArrayFlip, 'array_flip'); 88 | $phpench->addBenchmark(new BenchmarkArrayUnique, 'array_unique'); 89 | 90 | // Run the benchmark and plot the results in realtime. 91 | // With the second parameter you can specify 92 | // the start, end and step for each call 93 | $phpench->setInput(range(1,pow(2,16), 1024)); 94 | $phpench->setRepetitions(4); 95 | $phpench->run(); 96 | ``` 97 | 98 | ## Installation 99 | 100 | 1.) Add this package to your composer.json 101 | 102 | ``` 103 | { 104 | "require": { 105 | "mre/phpench": "*@dev" 106 | } 107 | } 108 | ``` 109 | 110 | 2.) Install gnuplot (Version 4.6) 111 | 112 | For *Mac OS X* you can install gnuplot via homebrew. For live generated charts you also need to install XQuartz. 113 | ``` 114 | Without X11 support: 115 | $ brew install homebrew/versions/gnuplot4 116 | 117 | With X11 supprt (recommended!): 118 | $ brew install homebrew/versions/gnuplot4 --with-x11 119 | ``` 120 | 121 | For *Linux* use your package manager. 122 | ``` 123 | apt-get install gnuplot 124 | ``` 125 | 126 | 3.) Look at the examples for usage 127 | 128 | ## Maintainers 129 | 130 | Matthias Endler (@matthiasendler) 131 | Markus Poerschke (@markuspoerschke) 132 | 133 | ## License 134 | 135 | Apache License Version 2.0 136 | -------------------------------------------------------------------------------- /bin/phpunit: -------------------------------------------------------------------------------- 1 | ../vendor/phpunit/phpunit/phpunit -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mre/phpench", 3 | "description": "A graphical PHP benchmarking tool", 4 | "keywords": ["php", "gnuplot", "graph", "plot", "stats"], 5 | "homepage": "https://github.com/mre/phpench", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Matthias Endler", 10 | "email": "matthias-endler@gmx.net", 11 | "homepage": "https://endler.dev" 12 | }, 13 | { 14 | "name": "Markus Poerschke", 15 | "email": "markus@eluceo.de", 16 | "homepage": "http://twitter.com/markuspoerschke" 17 | } 18 | ], 19 | "autoload": { 20 | "psr-0": { 21 | "mre": "src/" 22 | } 23 | }, 24 | "config": { 25 | "bin-dir": "bin" 26 | }, 27 | "require": { 28 | "php": ">=7.2.0", 29 | "abbadon1334/gnuplot": "*@dev", 30 | "phpunit/php-timer": "*@dev" 31 | }, 32 | "require-dev": { 33 | "phpunit/phpunit": "*" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/array_flip_vs_array_unique.php: -------------------------------------------------------------------------------- 1 | test = []; 19 | for ($i = 1; $i < $arrSize; $i++) { 20 | $this->test[$i] = $arrSize % $i; 21 | } 22 | 23 | return $this->test; 24 | } 25 | } 26 | 27 | class BenchmarkArrayFlip extends AbstractBenchmark 28 | { 29 | public function execute(): void 30 | { 31 | $test = array_flip(array_flip($this->test)); 32 | } 33 | } 34 | 35 | class BenchmarkArrayUnique extends AbstractBenchmark 36 | { 37 | public function execute(): void 38 | { 39 | $test = array_unique($this->test); 40 | } 41 | } 42 | 43 | // Create a new benchmark instance 44 | $phpench = new mre\PHPench(new \mre\PHPench\Aggregator\MedianAggregator()); 45 | 46 | // Use GnuPlot for output 47 | $oOutput = new \mre\PHPench\Output\GnuPlotOutput('test2.png', 1024, 768); 48 | 49 | // Alternatively, print the values to the terminal 50 | //$oOutput = new \mre\PHPench\Output\CliOutput(); 51 | 52 | $oOutput->setTitle('Compare array_flip and array_unique'); 53 | $phpench->setOutput($oOutput); 54 | 55 | // Add your test to the instance 56 | $phpench->addBenchmark(new BenchmarkArrayFlip(), 'array_flip'); 57 | $phpench->addBenchmark(new BenchmarkArrayUnique(), 'array_unique'); 58 | 59 | // Run the benchmark and plot the results in realtime. 60 | // With the second parameter you can specify 61 | // the start, end and step for each call 62 | $phpench->setInput(range(1, pow(2, 16), 1024)); 63 | $phpench->setRepetitions(4); 64 | $phpench->run(); 65 | -------------------------------------------------------------------------------- /examples/array_key_exists_vs_isset.php: -------------------------------------------------------------------------------- 1 | test = []; 19 | for ($i = 0; $i < $arrSize; ++$i) { 20 | //$str = uniqid(); 21 | $this->test['key_' . $i] = 'value_' . $i; 22 | } 23 | 24 | return $this->test; 25 | } 26 | } 27 | 28 | class BenchmarkIsset extends AbstractBenchmark 29 | { 30 | public function execute(): void 31 | { 32 | $bla = isset($this->test['doesnotexist']); 33 | } 34 | } 35 | 36 | class BenchmarkArrayKeyExists extends AbstractBenchmark 37 | { 38 | public function execute(): void 39 | { 40 | $bla = array_key_exists('doesnotexist', $this->test); 41 | } 42 | } 43 | 44 | // Create a new benchmark instance 45 | $phpench = new mre\PHPench(new \mre\PHPench\Aggregator\MedianAggregator()); 46 | 47 | // Use GnuPlot for output 48 | $oOutput = new \mre\PHPench\Output\GnuPlotOutput('test2.png', 1024, 768); 49 | 50 | // Alternatively, print the values to the terminal 51 | //$oOutput = new \mre\PHPench\Output\CliOutput(); 52 | 53 | $oOutput->setTitle('Compare isset and array_key_exists'); 54 | $phpench->setOutput($oOutput); 55 | 56 | // Add your test to the instance 57 | $phpench->addBenchmark(new BenchmarkIsset(), 'isset'); 58 | $phpench->addBenchmark(new BenchmarkArrayKeyExists(), 'array_key_exists'); 59 | 60 | // Run the benchmark and plot the results in realtime. 61 | // With the second parameter you can specify 62 | // the start, end and step for each call 63 | $phpench->setInput(range(1, pow(2, 16), 1024)); 64 | $phpench->setRepetitions(4); 65 | $phpench->run(); 66 | -------------------------------------------------------------------------------- /examples/compare_placeholder_replacement.php: -------------------------------------------------------------------------------- 1 | text = $this->createText(10); 20 | $this->placeholders = $this->createPlaceholders($arrSize); 21 | } 22 | 23 | protected function createText($n) 24 | { 25 | $text = 'Lorem Ipsum Text '; 26 | 27 | for ($i = 0; $i < $n; $i++) { 28 | $text .= ' placeholder: $placeholder_' . $i; 29 | } 30 | 31 | return $text; 32 | } 33 | 34 | protected function createPlaceholders($n) 35 | { 36 | $placeholders = []; 37 | 38 | for ($i = 0; $i < $n; $i++) { 39 | $placeholders['$placeholder_' . $i] = $i; 40 | } 41 | 42 | return $placeholders; 43 | } 44 | } 45 | 46 | class BenchmarkStringReplaceForeach extends AbstractBenchmark 47 | { 48 | public function execute(): void 49 | { 50 | foreach ($this->placeholders as $search => $replace) { 51 | $this->text = str_replace($search, $replace, $this->text); 52 | } 53 | } 54 | } 55 | 56 | class BenchmarkStringReplaceArrayValue extends AbstractBenchmark 57 | { 58 | public function execute(): void 59 | { 60 | $this->text = str_replace(array_keys($this->placeholders), array_values($this->placeholders), $this->text); 61 | } 62 | } 63 | 64 | class BenchmarkPregReplaceCallback extends AbstractBenchmark 65 | { 66 | public function execute(): void 67 | { 68 | $this->text = preg_replace_callback('/(\$[\w\d]+)\s/', function ($matches) { 69 | return isset($this->placeholders[$matches[0]]) ? $this->placeholders[$matches[0]] : $matches[0]; 70 | }, $this->text); 71 | } 72 | } 73 | 74 | // Create a new benchmark instance 75 | $phpench = new mre\PHPench(new \mre\PHPench\Aggregator\MedianAggregator()); 76 | $output = new \mre\PHPench\Output\GnuPlotOutput('test3.png', 1024, 768); 77 | $output->setTitle('Compare placeholder replacement'); 78 | $phpench->setOutput($output); 79 | 80 | // Add your test to the instance 81 | $phpench->addBenchmark(new BenchmarkStringReplaceForeach(), 'TestStringReplaceForeach'); 82 | $phpench->addBenchmark(new BenchmarkStringReplaceArrayValue(), 'TestStringReplaceArrayValue'); 83 | $phpench->addBenchmark(new BenchmarkPregReplaceCallback(), 'TestPregReplaceCallback'); 84 | 85 | // Run the benchmark and plot the results in realtime. 86 | // With the second parameter you can specify 87 | // the start, end and step for each call 88 | $phpench->setInput(range(0, 200, 2)); 89 | $phpench->setRepetitions(10); 90 | $phpench->run(); 91 | -------------------------------------------------------------------------------- /examples/compare_sort_algorithms.php: -------------------------------------------------------------------------------- 1 | test = []; 19 | for ($i = 0; $i < $arrSize; $i++) { 20 | $this->test[] = mt_rand(0, $arrSize * 10); 21 | } 22 | 23 | return $this->test; 24 | } 25 | } 26 | 27 | class BenchmarkBubbleSort extends AbstractBenchmark 28 | { 29 | private function bubblesort($arr = []) 30 | { 31 | $anz = count($arr); 32 | $temp = ''; 33 | for ($a = 0; $a < $anz; $a++) { 34 | for ($b = 0; $b < $anz - 1; $b++) { 35 | if ($arr[$b + 1] < $arr[$b]) { 36 | $temp = $arr[$b]; 37 | $arr[$b] = $arr[$b + 1]; 38 | $arr[$b + 1] = $temp; 39 | } 40 | } 41 | } 42 | 43 | return $arr; 44 | } 45 | 46 | public function execute(): void 47 | { 48 | $sorted = $this->bubblesort($this->test); 49 | } 50 | } 51 | 52 | class BenchmarkQuickSort extends AbstractBenchmark 53 | { 54 | private function quicksort($seq) 55 | { 56 | if (!count($seq)) { 57 | return $seq; 58 | } 59 | $pivot = $seq[0]; 60 | $low = $high = []; 61 | $length = count($seq); 62 | for ($i = 1; $i < $length; $i++) { 63 | if ($seq[$i] <= $pivot) { 64 | $low [] = $seq[$i]; 65 | } else { 66 | $high[] = $seq[$i]; 67 | } 68 | } 69 | 70 | return array_merge($this->quicksort($low), [$pivot], $this->quicksort($high)); 71 | } 72 | 73 | public function execute(): void 74 | { 75 | $sorted = $this->quicksort($this->test); 76 | } 77 | } 78 | 79 | // Create a new benchmark instance 80 | $phpench = new mre\PHPench(new \mre\PHPench\Aggregator\MedianAggregator()); 81 | $output = new \mre\PHPench\Output\GnuPlotOutput('sorting_algorithms.png', 1024, 768); 82 | $output->setTitle('Sorting Algorithms'); 83 | $phpench->setOutput($output); 84 | 85 | // Add your test to the instance 86 | $phpench->addBenchmark(new BenchmarkBubbleSort(), 'bubblesort'); 87 | $phpench->addBenchmark(new BenchmarkQuickSort(), 'quicksort'); 88 | 89 | // Run the benchmark and plot the results in realtime. 90 | // With the second parameter you can specify 91 | // the start, end and step for each call 92 | $phpench->setInput(range(1, pow(2, 16), 128)); 93 | $phpench->run(); 94 | -------------------------------------------------------------------------------- /examples/list_vs_set_lookup.php: -------------------------------------------------------------------------------- 1 | testList = []; 20 | $this->testSet = []; 21 | 22 | for ($i = 1; $i < $arrSize; $i++) { 23 | $randKey = rand(0, 1000 * $arrSize); 24 | 25 | // Set element in the list 26 | $this->testList[] = $randKey; 27 | 28 | // Set element in the set 29 | $this->testSet[$randKey] = true; 30 | } 31 | } 32 | } 33 | 34 | 35 | class BenchmarkListLookup extends AbstractBenchmark 36 | { 37 | public function execute() 38 | { 39 | $randKey = rand(1000, 10000); 40 | foreach ($this->testList as $id) { 41 | if ($randKey === $id) { 42 | // Key in array 43 | return true; 44 | } 45 | } 46 | // Key not in array 47 | return false; 48 | } 49 | } 50 | 51 | class BenchmarkSetLookup extends AbstractBenchmark 52 | { 53 | public function execute() 54 | { 55 | $randKey = rand(1000, 10000); 56 | 57 | return isset($this->testSet[$randKey]); 58 | } 59 | } 60 | 61 | // Create a new benchmark instance 62 | $phpench = new mre\PHPench(new \mre\PHPench\Aggregator\MedianAggregator()); 63 | 64 | // Use GnuPlot for output 65 | $oOutput = new \mre\PHPench\Output\GnuPlotOutput('test2.png', 1024, 768); 66 | 67 | // Alternatively, print the values to the terminal 68 | //$oOutput = new \mre\PHPench\Output\CliOutput(); 69 | $oOutput->setTitle('Compare set and list lookup in PHP'); 70 | $phpench->setOutput($oOutput); 71 | 72 | // Add your test to the instance 73 | $phpench->addBenchmark(new BenchmarkSetLookup(), 'set'); 74 | $phpench->addBenchmark(new BenchmarkListLookup(), 'list'); 75 | 76 | // Run the benchmark and plot the results in realtime. 77 | // With the second parameter you can specify 78 | // the start, end and step for each call 79 | $phpench->setInput(range(100, 1000, 10)); 80 | $phpench->setRepetitions(100); 81 | $phpench->run(); 82 | -------------------------------------------------------------------------------- /examples/single_quotes_vs_double_quotes.php: -------------------------------------------------------------------------------- 1 | setTitle('Compare single quote and double quote strings'); 35 | $phpench->setOutput($output); 36 | 37 | 38 | // Add your test to the instance 39 | $phpench->addBenchmark(new BenchmarkSingleQuotes(), 'single_quotes'); 40 | $phpench->addBenchmark(new BenchmarkDoubleQuotes(), 'double_quotes'); 41 | 42 | // Run the benchmark and plot the results in realtime. 43 | // With the second parameter you can specify 44 | // the start, end and step for each call 45 | $phpench->setInput(range(1, pow(2, 16), 512)); 46 | $phpench->setRepetitions(10000); 47 | $phpench->run(true); 48 | -------------------------------------------------------------------------------- /graph.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mre/PHPench/a72707335dd1253d1ee19f5c2b025d6e4d88144d/graph.gif -------------------------------------------------------------------------------- /src/mre/PHPench.php: -------------------------------------------------------------------------------- 1 | 19 | * @author Markus Poerschke 20 | */ 21 | class PHPench 22 | { 23 | private $tests = []; 24 | 25 | /** 26 | * @var OutputInterface 27 | */ 28 | private $output = null; 29 | 30 | /** 31 | * Contains an array with the run numbers. 32 | * 33 | * @var array 34 | */ 35 | private $input = []; 36 | 37 | /** 38 | * @var AggregatorInterface 39 | */ 40 | private $aggregator; 41 | 42 | /** 43 | * The number of times the bench should be executed. 44 | * 45 | * This can increase the precise. 46 | * 47 | * @var int 48 | */ 49 | private $repetitions = 3; 50 | 51 | public function __construct(AggregatorInterface $aggregator = null) 52 | { 53 | if (null === $aggregator) { 54 | $aggregator = new SimpleAggregator(); 55 | } 56 | 57 | $this->aggregator = $aggregator; 58 | } 59 | 60 | /** 61 | * sets output interface. 62 | * 63 | * @param OutputInterface $output 64 | */ 65 | public function setOutput(OutputInterface $output): void 66 | { 67 | $this->output = $output; 68 | } 69 | 70 | /** 71 | * Add a function to the benchmark. 72 | * 73 | * @param callable|BenchmarkInterface $test 74 | * @param string $title 75 | */ 76 | public function addBenchmark($test, $title): void 77 | { 78 | if (!$test instanceof \Closure && !$test instanceof BenchmarkInterface) { 79 | throw new \InvalidArgumentException('Test must be closure or implement TestInterface'); 80 | } 81 | 82 | $this->tests[] = $test; 83 | $this->output->addTest($title); 84 | } 85 | 86 | /** 87 | * Plots the graph for all added tests. 88 | * 89 | * @param bool $keepAlive 90 | */ 91 | public function run($keepAlive = false): void 92 | { 93 | for ($r = 1; $r <= $this->repetitions; ++$r) { 94 | foreach ($this->input as $i) { 95 | foreach ($this->tests as $index => $test) { 96 | $this->bench($test, $i, $index); 97 | } 98 | 99 | $this->output->update($this->aggregator, $i); 100 | } 101 | } 102 | 103 | $this->output->finalize($this->aggregator, $i); 104 | 105 | if ($keepAlive) { 106 | // Wait for user input to close 107 | echo 'Press enter to quit.'; 108 | fgets(STDIN); 109 | } 110 | } 111 | 112 | /** 113 | * @param array $input 114 | */ 115 | public function setInput(array $input): void 116 | { 117 | $this->input = $input; 118 | } 119 | 120 | /** 121 | * @param $repetitions 122 | */ 123 | public function setRepetitions($repetitions): void 124 | { 125 | $this->repetitions = $repetitions; 126 | } 127 | 128 | private function bench($benchFunction, $i, $index): void 129 | { 130 | if ($benchFunction instanceof BenchmarkInterface) { 131 | $benchFunction->setUp($i); 132 | Timer::start(); 133 | $benchFunction->execute(); 134 | $time = Timer::stop(); 135 | } else { 136 | Timer::start(); 137 | $benchFunction($i); 138 | $time = Timer::stop(); 139 | } 140 | 141 | $this->aggregator->push($i, $index, $time); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/mre/PHPench/Aggregator/AverageAggregator.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class AverageAggregator implements AggregatorInterface 13 | { 14 | private $data = []; 15 | 16 | public function push($i, $index, $value): void 17 | { 18 | if (!isset($this->data[$i][$index])) { 19 | $this->data[$i][$index] = $value; 20 | 21 | return; 22 | } 23 | 24 | $this->data[$i][$index] += $value; 25 | $this->data[$i][$index] /= 2; 26 | } 27 | 28 | public function getData() 29 | { 30 | return $this->data; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/mre/PHPench/Aggregator/MedianAggregator.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class MedianAggregator implements AggregatorInterface 14 | { 15 | private $data = []; 16 | 17 | public function push($i, $index, $value): void 18 | { 19 | $this->data[$i][$index][] = $value; 20 | } 21 | 22 | public function getData() 23 | { 24 | $data = []; 25 | 26 | foreach ($this->data as $i => $iterationResults) { 27 | foreach ($iterationResults as $index => $testResults) { 28 | $data[$i][$index] = Math::median($testResults); 29 | } 30 | } 31 | 32 | return $data; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/mre/PHPench/Aggregator/SimpleAggregator.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class SimpleAggregator implements AggregatorInterface 13 | { 14 | private $data = []; 15 | 16 | public function push($i, $index, $value): void 17 | { 18 | $this->data[$i][$index] = $value; 19 | } 20 | 21 | public function getData() 22 | { 23 | return $this->data; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/mre/PHPench/AggregatorInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface AggregatorInterface 11 | { 12 | /** 13 | * Adds a new value to the aggregator. 14 | * 15 | * @param $i 16 | * @param $index 17 | * @param $value 18 | */ 19 | public function push($i, $index, $value); 20 | 21 | /** 22 | * Returns the aggregated data as an array. 23 | * 24 | * array( 25 | * run_1 => array (TEST_1 => 123, TEST_2 => 456, ...) 26 | * ... 27 | * ) 28 | * 29 | * @return array 30 | */ 31 | public function getData(); 32 | } 33 | -------------------------------------------------------------------------------- /src/mre/PHPench/BenchmarkInterface.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface BenchmarkInterface 14 | { 15 | /** 16 | * Prepares the data. 17 | * 18 | * @param $i 19 | */ 20 | public function setUp($i); 21 | 22 | /** 23 | * Calls the tested function. 24 | */ 25 | public function execute(); 26 | } 27 | -------------------------------------------------------------------------------- /src/mre/PHPench/Output/CliOutput.php: -------------------------------------------------------------------------------- 1 | tests_titles as $index => $title) { 20 | printf('Test "%s" execution time: %f '.PHP_EOL, $title, @end(@$aggregator->getData())[$index]); 21 | } 22 | } 23 | 24 | /** 25 | * @param AggregatorInterface $aggregator 26 | * @param mixed $i 27 | */ 28 | public function finalize(AggregatorInterface $aggregator, $i): void 29 | { 30 | printf('Done'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/mre/PHPench/Output/GnuPlotOutput.php: -------------------------------------------------------------------------------- 1 | plot = new GnuPlot(); 38 | 39 | $this->filename = $filename; 40 | $this->width = $width; 41 | $this->height = $height; 42 | } 43 | 44 | /** 45 | * Updates plot data. 46 | * 47 | * @param AggregatorInterface $aggregator 48 | * @param int $i 49 | * 50 | * @return mixed|void 51 | */ 52 | public function update(AggregatorInterface $aggregator, $i) 53 | { 54 | $this->plot->reset(); 55 | $this->plot->setGraphTitle($this->title); 56 | $this->plot->setXLabel('run'); 57 | $this->plot->setYLabel('time'); 58 | 59 | // set titles 60 | foreach ($this->tests_titles as $index => $title) { 61 | $this->plot->setTitle($index, $title); 62 | } 63 | 64 | $data = $aggregator->getData(); 65 | foreach ($data as $pos => $results) { 66 | foreach ($results as $index => $resultValue) { 67 | $this->plot->push($pos, number_format($resultValue, $this->precision), $index); 68 | } 69 | } 70 | 71 | $this->plot->plot(); 72 | } 73 | 74 | /** 75 | * This method will save the graph as a PNG image. 76 | * 77 | * @param AggregatorInterface $aggregator 78 | * @param $i 79 | * 80 | * @return mixed|void 81 | */ 82 | public function finalize(AggregatorInterface $aggregator, $i) 83 | { 84 | $this->update($aggregator, $i); 85 | 86 | $this->plot->setWidth($this->width) 87 | ->setHeight($this->height) 88 | ->writePng($this->filename); 89 | } 90 | 91 | /** 92 | * @param int $precision 93 | * 94 | * @return GnuPlotOutput 95 | */ 96 | public function setPrecision(int $precision): GnuPlotOutput 97 | { 98 | $this->precision = $precision; 99 | 100 | return $this; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/mre/PHPench/Output/OutputAbstract.php: -------------------------------------------------------------------------------- 1 | title = $title; 24 | } 25 | 26 | public function addTest($tests_titles): void 27 | { 28 | $this->tests_titles[] = $tests_titles; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/mre/PHPench/Output/OutputInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Math 11 | { 12 | /** 13 | * Calculates the median of the given array. 14 | * 15 | * @param array $input 16 | * 17 | * @return float 18 | */ 19 | public static function median(array $input) 20 | { 21 | $count = count($input); 22 | 23 | if ($count < 1) { 24 | return 0; 25 | } elseif (1 === $count) { 26 | return reset($input); 27 | } 28 | 29 | // cleanup input array 30 | $input = array_values($input); 31 | sort($input, SORT_NUMERIC); 32 | 33 | if (0 === $count % 2) { 34 | $center = (int) floor($count / 2); 35 | 36 | return ($input[$center - 1] + $input[$center]) / 2; 37 | } 38 | 39 | return $input[(int) ($count / 2)]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mre/PHPench/a72707335dd1253d1ee19f5c2b025d6e4d88144d/test/.gitignore --------------------------------------------------------------------------------