├── .gitignore ├── .travis.yml ├── classes ├── Bench │ ├── ArrCallback.php │ ├── AutoLinkEmails.php │ ├── DateSpan.php │ ├── ExplodeLimit.php │ ├── GruberURL.php │ ├── LtrimDigits.php │ ├── MDDoBaseURL.php │ ├── MDDoImageURL.php │ ├── MDDoIncludeViews.php │ ├── StripNullBytes.php │ ├── Transliterate.php │ ├── URLSite.php │ ├── UserFuncArray.php │ ├── ValidColor.php │ └── ValidURL.php ├── Codebench.php ├── Controller │ └── Codebench.php └── Kohana │ └── Codebench.php ├── composer.json ├── config ├── codebench.php └── userguide.php ├── guide └── codebench │ ├── index.md │ └── menu.md ├── init.php ├── koharness.php ├── media └── guide │ └── codebench │ ├── codebench_screenshot1.png │ └── codebench_screenshot2.png └── views └── codebench.php /.gitignore: -------------------------------------------------------------------------------- 1 | Icon? 2 | .DS_Store 3 | .svn 4 | code_coverage 5 | *~ 6 | *.swp 7 | composer.lock 8 | vendor/* 9 | koharness_bootstrap.php 10 | 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: php 4 | 5 | # Only build the main develop/master branches - feature branches will be covered by PRs 6 | branches: 7 | only: 8 | - /^[0-9\.]+\/(develop|master)$/ 9 | 10 | cache: 11 | directories: 12 | - $HOME/.composer/cache/files 13 | 14 | php: 15 | - 5.3 16 | - 5.4 17 | - 5.5 18 | - 5.6 19 | - 7.0 20 | - hhvm 21 | 22 | matrix: 23 | include: 24 | - php: 5.3 25 | env: 'COMPOSER_PHPUNIT="lowest"' 26 | 27 | before_script: 28 | - composer self-update 29 | - composer install --prefer-dist --no-interaction 30 | - if [ "$COMPOSER_PHPUNIT" = "lowest" ]; then composer update --prefer-lowest --with-dependencies --prefer-dist --no-interaction phpunit/phpunit; fi; 31 | - vendor/bin/koharness 32 | 33 | script: 34 | - cd /tmp/koharness && ./vendor/bin/phpunit --bootstrap=modules/unittest/bootstrap.php modules/unittest/tests.php 35 | 36 | notifications: 37 | email: false 38 | -------------------------------------------------------------------------------- /classes/Bench/ArrCallback.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class Bench_ArrCallback extends Codebench { 8 | 9 | public $description = 10 | 'Parsing command[param,param] strings in Arr::callback(): 11 | http://github.com/shadowhand/kohana/commit/c3aaae849164bf92a486e29e736a265b350cb4da#L0R127'; 12 | 13 | public $loops = 10000; 14 | 15 | public $subjects = array 16 | ( 17 | // Valid callback strings 18 | 'foo', 19 | 'foo::bar', 20 | 'foo[apple,orange]', 21 | 'foo::bar[apple,orange]', 22 | '[apple,orange]', // no command, only params 23 | 'foo[[apple],[orange]]', // params with brackets inside 24 | 25 | // Invalid callback strings 26 | 'foo[apple,orange', // no closing bracket 27 | ); 28 | 29 | public function bench_shadowhand($subject) 30 | { 31 | // The original regex we're trying to optimize 32 | if (preg_match('/([^\[]*+)\[(.*)\]/', $subject, $match)) 33 | return $match; 34 | } 35 | 36 | public function bench_geert_regex_1($subject) 37 | { 38 | // Added ^ and $ around the whole pattern 39 | if (preg_match('/^([^\[]*+)\[(.*)\]$/', $subject, $matches)) 40 | return $matches; 41 | } 42 | 43 | public function bench_geert_regex_2($subject) 44 | { 45 | // A rather experimental approach using \K which requires PCRE 7.2 ~ PHP 5.2.4 46 | // Note: $matches[0] = params, $matches[1] = command 47 | if (preg_match('/^([^\[]*+)\[\K.*(?=\]$)/', $subject, $matches)) 48 | return $matches; 49 | } 50 | 51 | public function bench_geert_str($subject) 52 | { 53 | // A native string function approach which beats all the regexes 54 | if (strpos($subject, '[') !== FALSE AND substr($subject, -1) === ']') 55 | return explode('[', substr($subject, 0, -1), 2); 56 | } 57 | } -------------------------------------------------------------------------------- /classes/Bench/AutoLinkEmails.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class Bench_AutoLinkEmails extends Codebench { 8 | 9 | public $description = 10 | 'Fixing #2772, and comparing some possibilities.'; 11 | 12 | public $loops = 1000; 13 | 14 | public $subjects = array 15 | ( 16 | '', 20 | ); 21 | 22 | // The original function, with str_replace replaced by preg_replace. Looks clean. 23 | public function bench_match_all_loop($subject) 24 | { 25 | if (preg_match_all('~\b(?|58;)(?!\.)[-+_a-z0-9.]++(?|58;)(?!\.)[-+_a-z0-9.]++(?|58;)(?!\.)[-+_a-z0-9.]++(?|58;)(?!\.)[-+_a-z0-9.]++(? 6 | */ 7 | class Bench_DateSpan extends Codebench { 8 | 9 | public $description = 10 | 'Optimization for Date::span().'; 11 | 12 | public $loops = 1000; 13 | 14 | public $subjects = array(); 15 | 16 | public function __construct() 17 | { 18 | parent::__construct(); 19 | 20 | $this->subjects = array( 21 | time(), 22 | time() - Date::MONTH, 23 | time() - Date::YEAR, 24 | time() - Date::YEAR * 10, 25 | ); 26 | } 27 | 28 | // Original method 29 | public static function bench_span_original($remote, $local = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds') 30 | { 31 | // Array with the output formats 32 | $output = preg_split('/[^a-z]+/', strtolower( (string) $output)); 33 | 34 | // Invalid output 35 | if (empty($output)) 36 | return FALSE; 37 | 38 | // Make the output values into keys 39 | extract(array_flip($output), EXTR_SKIP); 40 | 41 | if ($local === NULL) 42 | { 43 | // Calculate the span from the current time 44 | $local = time(); 45 | } 46 | 47 | // Calculate timespan (seconds) 48 | $timespan = abs($remote - $local); 49 | 50 | if (isset($years)) 51 | { 52 | $timespan -= Date::YEAR * ($years = (int) floor($timespan / Date::YEAR)); 53 | } 54 | 55 | if (isset($months)) 56 | { 57 | $timespan -= Date::MONTH * ($months = (int) floor($timespan / Date::MONTH)); 58 | } 59 | 60 | if (isset($weeks)) 61 | { 62 | $timespan -= Date::WEEK * ($weeks = (int) floor($timespan / Date::WEEK)); 63 | } 64 | 65 | if (isset($days)) 66 | { 67 | $timespan -= Date::DAY * ($days = (int) floor($timespan / Date::DAY)); 68 | } 69 | 70 | if (isset($hours)) 71 | { 72 | $timespan -= Date::HOUR * ($hours = (int) floor($timespan / Date::HOUR)); 73 | } 74 | 75 | if (isset($minutes)) 76 | { 77 | $timespan -= Date::MINUTE * ($minutes = (int) floor($timespan / Date::MINUTE)); 78 | } 79 | 80 | // Seconds ago, 1 81 | if (isset($seconds)) 82 | { 83 | $seconds = $timespan; 84 | } 85 | 86 | // Remove the variables that cannot be accessed 87 | unset($timespan, $remote, $local); 88 | 89 | // Deny access to these variables 90 | $deny = array_flip(array('deny', 'key', 'difference', 'output')); 91 | 92 | // Return the difference 93 | $difference = array(); 94 | foreach ($output as $key) 95 | { 96 | if (isset($$key) AND ! isset($deny[$key])) 97 | { 98 | // Add requested key to the output 99 | $difference[$key] = $$key; 100 | } 101 | } 102 | 103 | // Invalid output formats string 104 | if (empty($difference)) 105 | return FALSE; 106 | 107 | // If only one output format was asked, don't put it in an array 108 | if (count($difference) === 1) 109 | return current($difference); 110 | 111 | // Return array 112 | return $difference; 113 | } 114 | 115 | // Using an array for the output 116 | public static function bench_span_use_array($remote, $local = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds') 117 | { 118 | // Array with the output formats 119 | $output = preg_split('/[^a-z]+/', strtolower( (string) $output)); 120 | 121 | // Invalid output 122 | if (empty($output)) 123 | return FALSE; 124 | 125 | // Convert the list of outputs to an associative array 126 | $output = array_combine($output, array_fill(0, count($output), 0)); 127 | 128 | // Make the output values into keys 129 | extract(array_flip($output), EXTR_SKIP); 130 | 131 | if ($local === NULL) 132 | { 133 | // Calculate the span from the current time 134 | $local = time(); 135 | } 136 | 137 | // Calculate timespan (seconds) 138 | $timespan = abs($remote - $local); 139 | 140 | if (isset($output['years'])) 141 | { 142 | $timespan -= Date::YEAR * ($output['years'] = (int) floor($timespan / Date::YEAR)); 143 | } 144 | 145 | if (isset($output['months'])) 146 | { 147 | $timespan -= Date::MONTH * ($output['months'] = (int) floor($timespan / Date::MONTH)); 148 | } 149 | 150 | if (isset($output['weeks'])) 151 | { 152 | $timespan -= Date::WEEK * ($output['weeks'] = (int) floor($timespan / Date::WEEK)); 153 | } 154 | 155 | if (isset($output['days'])) 156 | { 157 | $timespan -= Date::DAY * ($output['days'] = (int) floor($timespan / Date::DAY)); 158 | } 159 | 160 | if (isset($output['hours'])) 161 | { 162 | $timespan -= Date::HOUR * ($output['hours'] = (int) floor($timespan / Date::HOUR)); 163 | } 164 | 165 | if (isset($output['minutes'])) 166 | { 167 | $timespan -= Date::MINUTE * ($output['minutes'] = (int) floor($timespan / Date::MINUTE)); 168 | } 169 | 170 | // Seconds ago, 1 171 | if (isset($output['seconds'])) 172 | { 173 | $output['seconds'] = $timespan; 174 | } 175 | 176 | if (count($output) === 1) 177 | { 178 | // Only a single output was requested, return it 179 | return array_pop($output); 180 | } 181 | 182 | // Return array 183 | return $output; 184 | } 185 | 186 | } -------------------------------------------------------------------------------- /classes/Bench/ExplodeLimit.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class Bench_ExplodeLimit extends Codebench { 8 | 9 | public $description = 10 | 'Having a look at the effect of adding a limit to the explode function.
11 | http://stackoverflow.com/questions/1308149/how-to-get-a-part-of-url-between-4th-and-5th-slashes'; 12 | 13 | public $loops = 10000; 14 | 15 | public $subjects = array 16 | ( 17 | 'http://example.com/articles/123a/view', 18 | 'http://example.com/articles/123a/view/x/x/x/x/x', 19 | 'http://example.com/articles/123a/view/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x', 20 | ); 21 | 22 | public function bench_explode_without_limit($subject) 23 | { 24 | $parts = explode('/', $subject); 25 | return $parts[4]; 26 | } 27 | 28 | public function bench_explode_with_limit($subject) 29 | { 30 | $parts = explode('/', $subject, 6); 31 | return $parts[4]; 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /classes/Bench/GruberURL.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class Bench_GruberURL extends Codebench { 8 | 9 | public $description = 10 | 'Optimization for http://daringfireball.net/2009/11/liberal_regex_for_matching_urls'; 11 | 12 | public $loops = 10000; 13 | 14 | public $subjects = array 15 | ( 16 | 'http://foo.com/blah_blah', 17 | 'http://foo.com/blah_blah/', 18 | '(Something like http://foo.com/blah_blah)', 19 | 'http://foo.com/blah_blah_(wikipedia)', 20 | '(Something like http://foo.com/blah_blah_(wikipedia))', 21 | 'http://foo.com/blah_blah.', 22 | 'http://foo.com/blah_blah/.', 23 | '', 24 | '', 25 | 'http://foo.com/blah_blah,', 26 | 'http://www.example.com/wpstyle/?p=364.', 27 | 'http://✪df.ws/e7l', 28 | 'rdar://1234', 29 | 'rdar:/1234', 30 | 'x-yojimbo-item://6303E4C1-xxxx-45A6-AB9D-3A908F59AE0E', 31 | 'message://%3c330e7f8409726r6a4ba78dkf1fd71420c1bf6ff@mail.gmail.com%3e', 32 | 'http://➡.ws/䨹', 33 | 'www.➡.ws/䨹', 34 | 'http://example.com', 35 | 'Just a www.example.com link.', 36 | // To test the use of possessive quatifiers: 37 | 'httpppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp', 38 | ); 39 | 40 | public function bench_daringfireball($subject) 41 | { 42 | // Original regex by John Gruber 43 | preg_match('~\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))~', $subject, $matches); 44 | return (empty($matches)) ? FALSE : $matches[0]; 45 | } 46 | 47 | public function bench_daringfireball_v2($subject) 48 | { 49 | // Removed outer capturing parentheses, made another pair non-capturing 50 | preg_match('~\b(?:[\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|(?:[^[:punct:]\s]|/))~', $subject, $matches); 51 | return (empty($matches)) ? FALSE : $matches[0]; 52 | } 53 | 54 | public function bench_daringfireball_v3($subject) 55 | { 56 | // Made quantifiers possessive where possible 57 | preg_match('~\b(?:[\w-]++://?+|www[.])[^\s()<>]+(?:\([\w\d]++\)|(?:[^[:punct:]\s]|/))~', $subject, $matches); 58 | return (empty($matches)) ? FALSE : $matches[0]; 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /classes/Bench/LtrimDigits.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class Bench_LtrimDigits extends Codebench { 8 | 9 | public $description = 'Chopping off leading digits: regex vs ltrim.'; 10 | 11 | public $loops = 100000; 12 | 13 | public $subjects = array 14 | ( 15 | '123digits', 16 | 'no-digits', 17 | ); 18 | 19 | public function bench_regex($subject) 20 | { 21 | return preg_replace('/^\d+/', '', $subject); 22 | } 23 | 24 | public function bench_ltrim($subject) 25 | { 26 | return ltrim($subject, '0..9'); 27 | } 28 | } -------------------------------------------------------------------------------- /classes/Bench/MDDoBaseURL.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class Bench_MDDoBaseURL extends Codebench { 8 | 9 | public $description = 10 | 'Optimization for the doBaseURL() method of Kohana_Kodoc_Markdown 11 | for the Kohana Userguide.'; 12 | 13 | public $loops = 10000; 14 | 15 | public $subjects = array 16 | ( 17 | // Valid matches 18 | '[filesystem](about.filesystem)', 19 | '[filesystem](about.filesystem "Optional title")', 20 | '[same page link](#id)', 21 | '[object oriented](http://wikipedia.org/wiki/Object-Oriented_Programming)', 22 | 23 | // Invalid matches 24 | '![this is image syntax](about.filesystem)', 25 | '[filesystem](about.filesystem', 26 | ); 27 | 28 | public function bench_original($subject) 29 | { 30 | // The original regex contained a bug, which is fixed here for benchmarking purposes. 31 | // At the very start of the regex, (?!!) has been replace by (? 6 | */ 7 | class Bench_MDDoImageURL extends Codebench { 8 | 9 | public $description = 10 | 'Optimization for the doImageURL() method of Kohana_Kodoc_Markdown 11 | for the Kohana Userguide.'; 12 | 13 | public $loops = 10000; 14 | 15 | public $subjects = array 16 | ( 17 | // Valid matches 18 | '![Alt text](http://img.skitch.com/20091019-rud5mmqbf776jwua6hx9nm1n.png)', 19 | '![Alt text](https://img.skitch.com/20091019-rud5mmqbf776jwua6hx9nm1n.png)', 20 | '![Alt text](otherprotocol://image.png "Optional title")', 21 | '![Alt text](img/install.png "Optional title")', 22 | '![Alt text containing [square] brackets](img/install.png)', 23 | '![Empty src]()', 24 | 25 | // Invalid matches 26 | '![Alt text](img/install.png "No closing parenthesis"', 27 | ); 28 | 29 | public function bench_original($subject) 30 | { 31 | return preg_replace_callback('~!\[(.+?)\]\((\S*(?:\s*".+?")?)\)~', array($this, '_add_image_url_original'), $subject); 32 | } 33 | protected function _add_image_url_original($matches) 34 | { 35 | if ($matches[2] AND strpos($matches[2], '://') === FALSE) 36 | { 37 | // Add the base url to the link URL 38 | $matches[2] = 'http://BASE/'.$matches[2]; 39 | } 40 | 41 | // Recreate the link 42 | return "![{$matches[1]}]({$matches[2]})"; 43 | } 44 | 45 | public function bench_optimized_callback($subject) 46 | { 47 | // Moved the check for "://" to the regex, simplifying the callback function 48 | return preg_replace_callback('~!\[(.+?)\]\((?!\w++://)(\S*(?:\s*+".+?")?)\)~', array($this, '_add_image_url_optimized'), $subject); 49 | } 50 | protected function _add_image_url_optimized($matches) 51 | { 52 | // Add the base url to the link URL 53 | $matches[2] = 'http://BASE/'.$matches[2]; 54 | 55 | // Recreate the link 56 | return "![{$matches[1]}]({$matches[2]})"; 57 | } 58 | 59 | public function bench_callback_gone($subject) 60 | { 61 | // All the optimized callback was doing now, is prepend some text to the URL. 62 | // We don't need a callback for that, and that should be clearly faster. 63 | return preg_replace('~(!\[.+?\]\()(?!\w++://)(\S*(?:\s*+".+?")?\))~', '$1http://BASE/$2', $subject); 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /classes/Bench/MDDoIncludeViews.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class Bench_MDDoIncludeViews extends Codebench { 8 | 9 | public $description = 10 | 'Optimization for the doIncludeViews() method of Kohana_Kodoc_Markdown 11 | for the Kohana Userguide.'; 12 | 13 | public $loops = 10000; 14 | 15 | public $subjects = array 16 | ( 17 | // Valid matches 18 | '{{one}} two {{three}}', 19 | '{{userguide/examples/hello_world_error}}', 20 | 21 | // Invalid matches 22 | '{}', 23 | '{{}}', 24 | '{{userguide/examples/hello_world_error}', 25 | '{{userguide/examples/hello_world_error }}', 26 | '{{userguide/examples/{{hello_world_error }}', 27 | ); 28 | 29 | public function bench_original($subject) 30 | { 31 | preg_match_all('/{{(\S+?)}}/m', $subject, $matches, PREG_SET_ORDER); 32 | return $matches; 33 | } 34 | 35 | public function bench_possessive($subject) 36 | { 37 | // Using a possessive character class 38 | // Removed useless /m modifier 39 | preg_match_all('/{{([^\s{}]++)}}/', $subject, $matches, PREG_SET_ORDER); 40 | return $matches; 41 | } 42 | 43 | public function bench_lookaround($subject) 44 | { 45 | // Using lookaround to move $mathes[1] into $matches[0] 46 | preg_match_all('/(?<={{)[^\s{}]++(?=}})/', $subject, $matches, PREG_SET_ORDER); 47 | return $matches; 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /classes/Bench/StripNullBytes.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class Bench_StripNullBytes extends Codebench { 8 | 9 | public $description = 10 | 'String replacement comparisons related to #2676.'; 11 | 12 | public $loops = 1000; 13 | 14 | public $subjects = array 15 | ( 16 | "\0", 17 | "\0\0\0\0\0\0\0\0\0\0", 18 | "bla\0bla\0bla\0bla\0bla\0bla\0bla\0bla\0bla\0bla", 19 | "blablablablablablablablablablablablablablablabla", 20 | ); 21 | 22 | public function bench_str_replace($subject) 23 | { 24 | return str_replace("\0", '', $subject); 25 | } 26 | 27 | public function bench_strtr($subject) 28 | { 29 | return strtr($subject, array("\0" => '')); 30 | } 31 | 32 | public function bench_preg_replace($subject) 33 | { 34 | return preg_replace('~\0+~', '', $subject); 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /classes/Bench/Transliterate.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class Bench_Transliterate extends Codebench { 8 | 9 | public $description = 10 | 'Inspired by: 11 | http://forum.kohanaframework.org/comments.php?DiscussionID=6113'; 12 | 13 | public $loops = 10; 14 | 15 | public $subjects = array 16 | ( 17 | // ASCII 18 | 'a', 'b', 'c', 'd', '1', '2', '3', 19 | 20 | // Non-ASCII 21 | 'à', 'ô', 'ď', 'ḟ', 'ë', 'š', 'ơ', 22 | 'ß', 'ă', 'ř', 'ț', 'ň', 'ā', 'ķ', 23 | 'ŝ', 'ỳ', 'ņ', 'ĺ', 'ħ', 'ṗ', 'ó', 24 | 'ú', 'ě', 'é', 'ç', 'ẁ', 'ċ', 'õ', 25 | 'ṡ', 'ø', 'ģ', 'ŧ', 'ș', 'ė', 'ĉ', 26 | 'ś', 'î', 'ű', 'ć', 'ę', 'ŵ', 'ṫ', 27 | 'ū', 'č', 'ö', 'è', 'ŷ', 'ą', 'ł', 28 | 'ų', 'ů', 'ş', 'ğ', 'ļ', 'ƒ', 'ž', 29 | 'ẃ', 'ḃ', 'å', 'ì', 'ï', 'ḋ', 'ť', 30 | 'ŗ', 'ä', 'í', 'ŕ', 'ê', 'ü', 'ò', 31 | 'ē', 'ñ', 'ń', 'ĥ', 'ĝ', 'đ', 'ĵ', 32 | 'ÿ', 'ũ', 'ŭ', 'ư', 'ţ', 'ý', 'ő', 33 | 'â', 'ľ', 'ẅ', 'ż', 'ī', 'ã', 'ġ', 34 | 'ṁ', 'ō', 'ĩ', 'ù', 'į', 'ź', 'á', 35 | 'û', 'þ', 'ð', 'æ', 'µ', 'ĕ', 'ı', 36 | 'À', 'Ô', 'Ď', 'Ḟ', 'Ë', 'Š', 'Ơ', 37 | 'Ă', 'Ř', 'Ț', 'Ň', 'Ā', 'Ķ', 'Ĕ', 38 | 'Ŝ', 'Ỳ', 'Ņ', 'Ĺ', 'Ħ', 'Ṗ', 'Ó', 39 | 'Ú', 'Ě', 'É', 'Ç', 'Ẁ', 'Ċ', 'Õ', 40 | 'Ṡ', 'Ø', 'Ģ', 'Ŧ', 'Ș', 'Ė', 'Ĉ', 41 | 'Ś', 'Î', 'Ű', 'Ć', 'Ę', 'Ŵ', 'Ṫ', 42 | 'Ū', 'Č', 'Ö', 'È', 'Ŷ', 'Ą', 'Ł', 43 | 'Ų', 'Ů', 'Ş', 'Ğ', 'Ļ', 'Ƒ', 'Ž', 44 | 'Ẃ', 'Ḃ', 'Å', 'Ì', 'Ï', 'Ḋ', 'Ť', 45 | 'Ŗ', 'Ä', 'Í', 'Ŕ', 'Ê', 'Ü', 'Ò', 46 | 'Ē', 'Ñ', 'Ń', 'Ĥ', 'Ĝ', 'Đ', 'Ĵ', 47 | 'Ÿ', 'Ũ', 'Ŭ', 'Ư', 'Ţ', 'Ý', 'Ő', 48 | 'Â', 'Ľ', 'Ẅ', 'Ż', 'Ī', 'Ã', 'Ġ', 49 | 'Ṁ', 'Ō', 'Ĩ', 'Ù', 'Į', 'Ź', 'Á', 50 | 'Û', 'Þ', 'Ð', 'Æ', 'İ', 51 | ); 52 | 53 | public function bench_utf8($subject) 54 | { 55 | return UTF8::transliterate_to_ascii($subject); 56 | } 57 | 58 | public function bench_iconv($subject) 59 | { 60 | // Note: need to suppress errors on iconv because some chars trigger the following notice: 61 | // "Detected an illegal character in input string" 62 | return preg_replace('~[^-a-z0-9]+~i', '', @iconv('UTF-8', 'ASCII//TRANSLIT', $subject)); 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /classes/Bench/URLSite.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class Bench_URLSite extends Codebench { 8 | 9 | public $description = 'http://dev.kohanaframework.org/issues/3110'; 10 | 11 | public $loops = 1000; 12 | 13 | public $subjects = array 14 | ( 15 | '', 16 | 'news', 17 | 'news/', 18 | '/news/', 19 | 'news/page/5', 20 | 'news/page:5', 21 | 'http://example.com/', 22 | 'http://example.com/hello', 23 | 'http://example.com:80/', 24 | 'http://user:pass@example.com/', 25 | ); 26 | 27 | public function __construct() 28 | { 29 | foreach ($this->subjects as $subject) 30 | { 31 | // Automatically create URIs with query string and/or fragment part appended 32 | $this->subjects[] = $subject.'?query=string'; 33 | $this->subjects[] = $subject.'#fragment'; 34 | $this->subjects[] = $subject.'?query=string#fragment'; 35 | } 36 | 37 | parent::__construct(); 38 | } 39 | 40 | public function bench_original($uri) 41 | { 42 | // Get the path from the URI 43 | $path = trim(parse_url($uri, PHP_URL_PATH), '/'); 44 | 45 | if ($query = parse_url($uri, PHP_URL_QUERY)) 46 | { 47 | $query = '?'.$query; 48 | } 49 | 50 | if ($fragment = parse_url($uri, PHP_URL_FRAGMENT)) 51 | { 52 | $fragment = '#'.$fragment; 53 | } 54 | 55 | return $path.$query.$fragment; 56 | } 57 | 58 | public function bench_explode($uri) 59 | { 60 | // Chop off possible scheme, host, port, user and pass parts 61 | $path = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/')); 62 | 63 | $fragment = ''; 64 | $explode = explode('#', $path, 2); 65 | if (isset($explode[1])) 66 | { 67 | $path = $explode[0]; 68 | $fragment = '#'.$explode[1]; 69 | } 70 | 71 | $query = ''; 72 | $explode = explode('?', $path, 2); 73 | if (isset($explode[1])) 74 | { 75 | $path = $explode[0]; 76 | $query = '?'.$explode[1]; 77 | } 78 | 79 | return $path.$query.$fragment; 80 | } 81 | 82 | public function bench_regex($uri) 83 | { 84 | preg_match('~^(?:[-a-z0-9+.]++://[^/]++/?)?([^?#]++)?(\?[^#]*+)?(#.*)?~', trim($uri, '/'), $matches); 85 | $path = Arr::get($matches, 1, ''); 86 | $query = Arr::get($matches, 2, ''); 87 | $fragment = Arr::get($matches, 3, ''); 88 | 89 | return $path.$query.$fragment; 90 | } 91 | 92 | public function bench_regex_without_arrget($uri) 93 | { 94 | preg_match('~^(?:[-a-z0-9+.]++://[^/]++/?)?([^?#]++)?(\?[^#]*+)?(#.*)?~', trim($uri, '/'), $matches); 95 | $path = isset($matches[1]) ? $matches[1] : ''; 96 | $query = isset($matches[2]) ? $matches[2] : ''; 97 | $fragment = isset($matches[3]) ? $matches[3] : ''; 98 | 99 | return $path.$query.$fragment; 100 | } 101 | 102 | // And then I thought, why do all the work of extracting the query and fragment parts and then reappending them? 103 | // Just leaving them alone should be fine, right? As a bonus we get a very nice speed boost. 104 | public function bench_less_is_more($uri) 105 | { 106 | // Chop off possible scheme, host, port, user and pass parts 107 | $path = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/')); 108 | 109 | return $path; 110 | } 111 | 112 | public function bench_less_is_more_with_strpos_optimization($uri) 113 | { 114 | if (strpos($uri, '://') !== FALSE) 115 | { 116 | // Chop off possible scheme, host, port, user and pass parts 117 | $uri = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/')); 118 | } 119 | 120 | return $uri; 121 | } 122 | 123 | } -------------------------------------------------------------------------------- /classes/Bench/UserFuncArray.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class Bench_UserFuncArray extends Codebench { 8 | 9 | public $description = 10 | 'Testing the speed difference of using call_user_func_array 11 | compared to counting args and doing manual calls.'; 12 | 13 | public $loops = 100000; 14 | 15 | public $subjects = array 16 | ( 17 | // Argument sets 18 | array(), 19 | array('one'), 20 | array('one', 'two'), 21 | array('one', 'two', 'three'), 22 | ); 23 | 24 | public function bench_count_args($args) 25 | { 26 | $name = 'callme'; 27 | switch (count($args)) 28 | { 29 | case 1: 30 | $this->$name($args[0]); 31 | break; 32 | case 2: 33 | $this->$name($args[0], $args[1]); 34 | break; 35 | case 3: 36 | $this->$name($args[0], $args[1], $args[2]); 37 | break; 38 | case 4: 39 | $this->$name($args[0], $args[1], $args[2], $args[3]); 40 | break; 41 | default: 42 | call_user_func_array(array($this, $name), $args); 43 | break; 44 | } 45 | } 46 | 47 | public function bench_direct_call($args) 48 | { 49 | $name = 'callme'; 50 | call_user_func_array(array($this, $name), $args); 51 | } 52 | 53 | protected function callme() 54 | { 55 | return count(func_get_args()); 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /classes/Bench/ValidColor.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class Bench_ValidColor extends Codebench { 8 | 9 | public $description = 10 | 'Optimization for Validate::color(). 11 | See: http://forum.kohanaphp.com/comments.php?DiscussionID=2192. 12 | 13 | Note that the methods with an _invalid suffix contain flawed regexes and should be 14 | completely discarded. I left them in here for educational purposes, and to remind myself 15 | to think harder and test more thoroughly. It can\'t be that I only found out so late in 16 | the game. For the regex explanation have a look at the forum topic mentioned earlier.'; 17 | 18 | public $loops = 10000; 19 | 20 | public $subjects = array 21 | ( 22 | // Valid colors 23 | 'aaA', 24 | '123', 25 | '000000', 26 | '#123456', 27 | '#abcdef', 28 | 29 | // Invalid colors 30 | 'ggg', 31 | '1234', 32 | '#1234567', 33 | "#000\n", 34 | '}§è!çà%$z', 35 | ); 36 | 37 | // Note that I added the D modifier to corey's regexes. We need to match exactly 38 | // the same if we want the benchmarks to be of any value. 39 | public function bench_corey_regex_1_invalid($subject) 40 | { 41 | return (bool) preg_match('/^#?([0-9a-f]{1,2}){3}$/iD', $subject); 42 | } 43 | 44 | public function bench_corey_regex_2($subject) 45 | { 46 | return (bool) preg_match('/^#?([0-9a-f]){3}(([0-9a-f]){3})?$/iD', $subject); 47 | } 48 | 49 | // Optimized corey_regex_1 50 | // Using non-capturing parentheses and a possessive interval 51 | public function bench_geert_regex_1a_invalid($subject) 52 | { 53 | return (bool) preg_match('/^#?(?:[0-9a-f]{1,2}+){3}$/iD', $subject); 54 | } 55 | 56 | // Optimized corey_regex_2 57 | // Removed useless parentheses, made the remaining ones non-capturing 58 | public function bench_geert_regex_2a($subject) 59 | { 60 | return (bool) preg_match('/^#?[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $subject); 61 | } 62 | 63 | // Optimized geert_regex_1a 64 | // Possessive "#" 65 | public function bench_geert_regex_1b_invalid($subject) 66 | { 67 | return (bool) preg_match('/^#?+(?:[0-9a-f]{1,2}+){3}$/iD', $subject); 68 | } 69 | 70 | // Optimized geert_regex_2a 71 | // Possessive "#" 72 | public function bench_geert_regex_2b($subject) 73 | { 74 | return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $subject); 75 | } 76 | 77 | // Using \z instead of $ 78 | public function bench_salathe_regex_1($subject) 79 | { 80 | return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?\z/i', $subject); 81 | } 82 | 83 | // Using \A instead of ^ 84 | public function bench_salathe_regex_2($subject) 85 | { 86 | return (bool) preg_match('/\A#?+[0-9a-f]{3}(?:[0-9a-f]{3})?\z/i', $subject); 87 | } 88 | 89 | // A solution without regex 90 | public function bench_geert_str($subject) 91 | { 92 | if ($subject[0] === '#') 93 | { 94 | $subject = substr($subject, 1); 95 | } 96 | 97 | $strlen = strlen($subject); 98 | return (($strlen === 3 OR $strlen === 6) AND ctype_xdigit($subject)); 99 | } 100 | 101 | // An ugly, but fast, solution without regex 102 | public function bench_salathe_str($subject) 103 | { 104 | if ($subject[0] === '#') 105 | { 106 | $subject = substr($subject, 1); 107 | } 108 | 109 | // TRUE if: 110 | // 1. $subject is 6 or 3 chars long 111 | // 2. $subject contains only hexadecimal digits 112 | return (((isset($subject[5]) AND ! isset($subject[6])) OR 113 | (isset($subject[2]) AND ! isset($subject[3]))) 114 | AND ctype_xdigit($subject)); 115 | } 116 | } -------------------------------------------------------------------------------- /classes/Bench/ValidURL.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class Bench_ValidURL extends Codebench { 8 | 9 | public $description = 10 | 'filter_var vs regex: 11 | http://dev.kohanaframework.org/issues/2847'; 12 | 13 | public $loops = 1000; 14 | 15 | public $subjects = array 16 | ( 17 | // Valid 18 | 'http://google.com', 19 | 'http://google.com/', 20 | 'http://google.com/?q=abc', 21 | 'http://google.com/#hash', 22 | 'http://localhost', 23 | 'http://hello-world.pl', 24 | 'http://hello--world.pl', 25 | 'http://h.e.l.l.0.pl', 26 | 'http://server.tld/get/info', 27 | 'http://127.0.0.1', 28 | 'http://127.0.0.1:80', 29 | 'http://user@127.0.0.1', 30 | 'http://user:pass@127.0.0.1', 31 | 'ftp://my.server.com', 32 | 'rss+xml://rss.example.com', 33 | 34 | // Invalid 35 | 'http://google.2com', 36 | 'http://google.com?q=abc', 37 | 'http://google.com#hash', 38 | 'http://hello-.pl', 39 | 'http://hel.-lo.world.pl', 40 | 'http://ww£.google.com', 41 | 'http://127.0.0.1234', 42 | 'http://127.0.0.1.1', 43 | 'http://user:@127.0.0.1', 44 | "http://finalnewline.com\n", 45 | ); 46 | 47 | public function bench_filter_var($url) 48 | { 49 | return (bool) filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED); 50 | } 51 | 52 | public function bench_regex($url) 53 | { 54 | // Based on http://www.apps.ietf.org/rfc/rfc1738.html#sec-5 55 | if ( ! preg_match( 56 | '~^ 57 | 58 | # scheme 59 | [-a-z0-9+.]++:// 60 | 61 | # username:password (optional) 62 | (?: 63 | [-a-z0-9$_.+!*\'(),;?&=%]++ # username 64 | (?::[-a-z0-9$_.+!*\'(),;?&=%]++)? # password (optional) 65 | @ 66 | )? 67 | 68 | (?: 69 | # ip address 70 | \d{1,3}+(?:\.\d{1,3}+){3}+ 71 | 72 | | # or 73 | 74 | # hostname (captured) 75 | ( 76 | (?!-)[-a-z0-9]{1,63}+(? 253) 97 | return FALSE; 98 | 99 | // An extra check for the top level domain 100 | // It must start with a letter 101 | $tld = ltrim(substr($matches[1], (int) strrpos($matches[1], '.')), '.'); 102 | return ctype_alpha($tld[0]); 103 | } 104 | 105 | } -------------------------------------------------------------------------------- /classes/Codebench.php: -------------------------------------------------------------------------------- 1 | request->param('class'); 19 | 20 | // Convert submitted class name to URI segment 21 | if (isset($_POST['class'])) 22 | { 23 | throw HTTP_Exception::factory(302)->location('codebench/'.trim($_POST['class'])); 24 | } 25 | 26 | // Pass the class name on to the view 27 | $this->template->class = (string) $class; 28 | 29 | // Try to load the class, then run it 30 | if (Kohana::auto_load($class) === TRUE) 31 | { 32 | $codebench = new $class; 33 | $this->template->codebench = $codebench->run(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /classes/Kohana/Codebench.php: -------------------------------------------------------------------------------- 1 | 'A', 35 | 150 => 'B', 36 | 200 => 'C', 37 | 300 => 'D', 38 | 500 => 'E', 39 | 'default' => 'F', 40 | ); 41 | 42 | /** 43 | * Constructor. 44 | * 45 | * @return void 46 | */ 47 | public function __construct() 48 | { 49 | // Set the maximum execution time 50 | set_time_limit(Kohana::$config->load('codebench')->max_execution_time); 51 | } 52 | 53 | /** 54 | * Runs Codebench on the extending class. 55 | * 56 | * @return array benchmark output 57 | */ 58 | public function run() 59 | { 60 | // Array of all methods to loop over 61 | $methods = array_filter(get_class_methods($this), array($this, '_method_filter')); 62 | 63 | // Make sure the benchmark runs at least once, 64 | // also if no subject data has been provided. 65 | if (empty($this->subjects)) 66 | { 67 | $this->subjects = array('NULL' => NULL); 68 | } 69 | 70 | // Initialize benchmark output 71 | $codebench = array 72 | ( 73 | 'class' => get_class($this), 74 | 'description' => $this->description, 75 | 'loops' => array 76 | ( 77 | 'base' => (int) $this->loops, 78 | 'total' => (int) $this->loops * count($this->subjects) * count($methods), 79 | ), 80 | 'subjects' => $this->subjects, 81 | 'benchmarks' => array(), 82 | ); 83 | 84 | // Benchmark each method 85 | foreach ($methods as $method) 86 | { 87 | // Initialize benchmark output for this method 88 | $codebench['benchmarks'][$method] = array('time' => 0, 'memory' => 0); 89 | 90 | // Using Reflection because simply calling $this->$method($subject) in the loop below 91 | // results in buggy benchmark times correlating to the length of the method name. 92 | $reflection = new ReflectionMethod(get_class($this), $method); 93 | 94 | // Benchmark each subject on each method 95 | foreach ($this->subjects as $subject_key => $subject) 96 | { 97 | // Prerun each method/subject combo before the actual benchmark loop. 98 | // This way relatively expensive initial processes won't be benchmarked, e.g. autoloading. 99 | // At the same time we capture the return here so we don't have to do that in the loop anymore. 100 | $return = $reflection->invoke($this, $subject); 101 | 102 | // Start the timer for one subject 103 | $token = Profiler::start('codebench', $method.$subject_key); 104 | 105 | // The heavy work 106 | for ($i = 0; $i < $this->loops; ++$i) 107 | { 108 | $reflection->invoke($this, $subject); 109 | } 110 | 111 | // Stop and read the timer 112 | $benchmark = Profiler::total($token); 113 | 114 | // Benchmark output specific to the current method and subject 115 | $codebench['benchmarks'][$method]['subjects'][$subject_key] = array 116 | ( 117 | 'return' => $return, 118 | 'time' => $benchmark[0], 119 | 'memory' => $benchmark[1], 120 | ); 121 | 122 | // Update method totals 123 | $codebench['benchmarks'][$method]['time'] += $benchmark[0]; 124 | $codebench['benchmarks'][$method]['memory'] += $benchmark[1]; 125 | } 126 | } 127 | 128 | // Initialize the fastest and slowest benchmarks for both methods and subjects, time and memory, 129 | // these values will be overwritten using min() and max() later on. 130 | // The 999999999 values look like a hack, I know, but they work, 131 | // unless your method runs for more than 31 years or consumes over 1GB of memory. 132 | $fastest_method = $fastest_subject = array('time' => 999999999, 'memory' => 999999999); 133 | $slowest_method = $slowest_subject = array('time' => 0, 'memory' => 0); 134 | 135 | // Find the fastest and slowest benchmarks, needed for the percentage calculations 136 | foreach ($methods as $method) 137 | { 138 | // Update the fastest and slowest method benchmarks 139 | $fastest_method['time'] = min($fastest_method['time'], $codebench['benchmarks'][$method]['time']); 140 | $fastest_method['memory'] = min($fastest_method['memory'], $codebench['benchmarks'][$method]['memory']); 141 | $slowest_method['time'] = max($slowest_method['time'], $codebench['benchmarks'][$method]['time']); 142 | $slowest_method['memory'] = max($slowest_method['memory'], $codebench['benchmarks'][$method]['memory']); 143 | 144 | foreach ($this->subjects as $subject_key => $subject) 145 | { 146 | // Update the fastest and slowest subject benchmarks 147 | $fastest_subject['time'] = min($fastest_subject['time'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['time']); 148 | $fastest_subject['memory'] = min($fastest_subject['memory'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['memory']); 149 | $slowest_subject['time'] = max($slowest_subject['time'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['time']); 150 | $slowest_subject['memory'] = max($slowest_subject['memory'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['memory']); 151 | } 152 | } 153 | 154 | // Percentage calculations for methods 155 | foreach ($codebench['benchmarks'] as & $method) 156 | { 157 | // Calculate percentage difference relative to fastest and slowest methods 158 | $method['percent']['fastest']['time'] = (empty($fastest_method['time'])) ? 0 : ($method['time'] / $fastest_method['time'] * 100); 159 | $method['percent']['fastest']['memory'] = (empty($fastest_method['memory'])) ? 0 : ($method['memory'] / $fastest_method['memory'] * 100); 160 | $method['percent']['slowest']['time'] = (empty($slowest_method['time'])) ? 0 : ($method['time'] / $slowest_method['time'] * 100); 161 | $method['percent']['slowest']['memory'] = (empty($slowest_method['memory'])) ? 0 : ($method['memory'] / $slowest_method['memory'] * 100); 162 | 163 | // Assign a grade for time and memory to each method 164 | $method['grade']['time'] = $this->_grade($method['percent']['fastest']['time']); 165 | $method['grade']['memory'] = $this->_grade($method['percent']['fastest']['memory']); 166 | 167 | // Percentage calculations for subjects 168 | foreach ($method['subjects'] as & $subject) 169 | { 170 | // Calculate percentage difference relative to fastest and slowest subjects for this method 171 | $subject['percent']['fastest']['time'] = (empty($fastest_subject['time'])) ? 0 : ($subject['time'] / $fastest_subject['time'] * 100); 172 | $subject['percent']['fastest']['memory'] = (empty($fastest_subject['memory'])) ? 0 : ($subject['memory'] / $fastest_subject['memory'] * 100); 173 | $subject['percent']['slowest']['time'] = (empty($slowest_subject['time'])) ? 0 : ($subject['time'] / $slowest_subject['time'] * 100); 174 | $subject['percent']['slowest']['memory'] = (empty($slowest_subject['memory'])) ? 0 : ($subject['memory'] / $slowest_subject['memory'] * 100); 175 | 176 | // Assign a grade letter for time and memory to each subject 177 | $subject['grade']['time'] = $this->_grade($subject['percent']['fastest']['time']); 178 | $subject['grade']['memory'] = $this->_grade($subject['percent']['fastest']['memory']); 179 | } 180 | } 181 | 182 | return $codebench; 183 | } 184 | 185 | /** 186 | * Callback for array_filter(). 187 | * Filters out all methods not to benchmark. 188 | * 189 | * @param string method name 190 | * @return boolean 191 | */ 192 | protected function _method_filter($method) 193 | { 194 | // Only benchmark methods with the "bench" prefix 195 | return (substr($method, 0, 5) === 'bench'); 196 | } 197 | 198 | /** 199 | * Returns the applicable grade letter for a score. 200 | * 201 | * @param integer|double score 202 | * @return string grade letter 203 | */ 204 | protected function _grade($score) 205 | { 206 | foreach ($this->grades as $max => $grade) 207 | { 208 | if ($max === 'default') 209 | continue; 210 | 211 | if ($score <= $max) 212 | return $grade; 213 | } 214 | 215 | return $this->grades['default']; 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kohana/codebench", 3 | "type": "kohana-module", 4 | "description": "The official Kohana benchmarking module", 5 | "homepage": "http://kohanaframework.org", 6 | "license": "BSD-3-Clause", 7 | "keywords": ["kohana", "framework", "benchmarking"], 8 | "authors": [ 9 | { 10 | "name": "Kohana Team", 11 | "email": "team@kohanaframework.org", 12 | "homepage": "http://kohanaframework.org/team", 13 | "role": "developer" 14 | } 15 | ], 16 | "support": { 17 | "issues": "http://dev.kohanaframework.org", 18 | "forum": "http://forum.kohanaframework.org", 19 | "irc": "irc://irc.freenode.net/kohana", 20 | "source": "http://github.com/kohana/core" 21 | }, 22 | "require": { 23 | "composer/installers": "~1.0", 24 | "kohana/core": ">=3.3", 25 | "php": ">=5.3.3" 26 | }, 27 | "require-dev": { 28 | "kohana/core": "3.3.*@dev", 29 | "kohana/unittest": "3.3.*@dev", 30 | "kohana/koharness": "*@dev" 31 | }, 32 | "extra": { 33 | "branch-alias": { 34 | "dev-3.3/develop": "3.3.x-dev", 35 | "dev-3.4/develop": "3.4.x-dev" 36 | }, 37 | "installer-paths": { 38 | "vendor/{$vendor}/{$name}": ["type:kohana-module"] 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /config/codebench.php: -------------------------------------------------------------------------------- 1 | 0, 10 | 11 | /** 12 | * Expand all benchmark details by default. 13 | */ 14 | 'expand_all' => FALSE, 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /config/userguide.php: -------------------------------------------------------------------------------- 1 | array( 6 | 7 | // This should be the path to this modules userguide pages, without the 'guide/'. Ex: '/guide/modulename/' would be 'modulename' 8 | 'codebench' => array( 9 | 10 | // Whether this modules userguide pages should be shown 11 | 'enabled' => TRUE, 12 | 13 | // The name that should show up on the userguide index page 14 | 'name' => 'Codebench', 15 | 16 | // A short description of this module, shown on the index page 17 | 'description' => 'Code benchmarking tool.', 18 | 19 | // Copyright message, shown in the footer for this module 20 | 'copyright' => '© 2008–2012 Kohana Team', 21 | ) 22 | ) 23 | ); -------------------------------------------------------------------------------- /guide/codebench/index.md: -------------------------------------------------------------------------------- 1 | # Using Codebench 2 | 3 | [!!] The contents of this page are taken (with some minor changes) from and are copyright Geert De Deckere. 4 | 5 | For a long time I have been using a quick-and-dirty `benchmark.php` file to optimize bits of PHP code, many times regex-related stuff. The file contained not much more than a [gettimeofday](http://php.net/gettimeofday) function wrapped around a `for` loop. It worked, albeit not very efficiently. Something more solid was needed. I set out to create a far more usable piece of software to aid in the everlasting quest to squeeze every millisecond out of those regular expressions. 6 | 7 | ## Codebench Goals 8 | 9 | ### Benchmark multiple regular expressions at once 10 | 11 | Being able to compare the speed of an arbitrary amount of regular expressions would be tremendously useful. In case you are wondering—yes, I had been writing down benchmark times for each regex, uncommenting them one by one. You get the idea. Those days should be gone forever now. 12 | 13 | ### Benchmark multiple subjects at once 14 | 15 | What gets overlooked too often when testing and optimizing regular expressions is the fact that speed can vastly differ depending on the subjects, also known as input or target strings. Just because your regular expression matches, say, a valid email address quickly, does not necessarily mean it will quickly realize when an invalid email is provided. I plan to write a follow-up article with hands-on regex examples to demonstrate this point. Anyway, Codebench allows you to create an array of subjects which will be passed to each benchmark. 16 | 17 | ### Make it flexible enough to work for all PCRE functions 18 | 19 | Initially I named the module “Regexbench”. I quickly realized, though, it would be flexible enough to benchmark all kinds of PHP code, hence the change to “Codebench”. While tools specifically built to help profiling PCRE functions, like [preg_match](http://php.net/preg_match) or [preg_replace](http://php.net/preg_replace), definitely have their use, more flexibility was needed here. You should be able to compare all kinds of constructions like combinations of PCRE functions and native PHP string functions. 20 | 21 | ### Create clean and portable benchmark cases 22 | 23 | Throwing valuable benchmark data away every time I needed to optimize another regular expression had to stop. A clean file containing the complete set of all regex variations to compare, together with the set of subjects to test them against, would be more than welcome. Moreover, it would be easy to exchange benchmark cases with others. 24 | 25 | ### Visualize the benchmarks 26 | 27 | Obviously providing a visual representation of the benchmark results, via simple graphs, would make interpreting them easier. Having not to think about Internet Explorer for once, made writing CSS a whole lot more easy and fun. It resulted in some fine graphs which are fully resizable. 28 | 29 | Below are two screenshots of Codebench in action. `Valid_Color` is a class made for benchmarking different ways to validate hexadecimal HTML color values, e.g. `#FFF`. If you are interested in the story behind the actual regular expressions, take a look at [this topic in the Kohana forums](http://forum.kohanaframework.org/discussion/2192). 30 | 31 | ![Benchmarking several ways to validate HTML color values](codebench_screenshot1.png) 32 | **Benchmarking seven ways to validate HTML color values** 33 | 34 | ![Collapsable results per subject for each method](codebench_screenshot2.png) 35 | **Collapsable results per subject for each method** 36 | 37 | ## Working with Codebench 38 | 39 | Codebench is included in Kohana 3, but if you need you [can download it](http://github.com/kohana/codebench/) from GitHub. Be sure Codebench is activated in your `application/bootstrap.php`. 40 | 41 | Creating your own benchmarks is just a matter of creating a class that extends the Codebench class. The class should go in `classes/bench` and the class name should have the `Bench_` prefix. Put the code parts you want to compare into separate methods. Be sure to prefix those methods with `bench_`, other methods will not be benchmarked. Glance at the files in `modules/codebench/classes/bench/` for more examples. 42 | 43 | Here is another short example with some extra explanations. 44 | 45 | // classes/bench/ltrimdigits.php 46 | class Bench_LtrimDigits extends Codebench { 47 | 48 | // Some optional explanatory comments about the benchmark file. 49 | // HTML allowed. URLs will be converted to links automatically. 50 | public $description = 'Chopping off leading digits: regex vs ltrim.'; 51 | 52 | // How many times to execute each method per subject. 53 | // Total loops = loops * number of methods * number of subjects 54 | public $loops = 100000; 55 | 56 | // The subjects to supply iteratively to your benchmark methods. 57 | public $subjects = array 58 | ( 59 | '123digits', 60 | 'no-digits', 61 | ); 62 | 63 | public function bench_regex($subject) 64 | { 65 | return preg_replace('/^\d+/', '', $subject); 66 | } 67 | 68 | public function bench_ltrim($subject) 69 | { 70 | return ltrim($subject, '0..9'); 71 | } 72 | } 73 | 74 | 75 | 76 | And the winner is… [ltrim](http://php.net/ltrim). Happy benchmarking! 77 | -------------------------------------------------------------------------------- /guide/codebench/menu.md: -------------------------------------------------------------------------------- 1 | ## [Codebench]() -------------------------------------------------------------------------------- /init.php: -------------------------------------------------------------------------------- 1 | )') 5 | ->defaults(array( 6 | 'controller' => 'Codebench', 7 | 'action' => 'index', 8 | 'class' => NULL)); 9 | -------------------------------------------------------------------------------- /koharness.php: -------------------------------------------------------------------------------- 1 | array( 5 | 'codebench' => __DIR__, 6 | 'unittest' => __DIR__ . '/vendor/kohana/unittest' 7 | ), 8 | ); 9 | -------------------------------------------------------------------------------- /media/guide/codebench/codebench_screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kohana/codebench/3bde69a90e8761c797608aeb33a504f24dcd4ae4/media/guide/codebench/codebench_screenshot1.png -------------------------------------------------------------------------------- /media/guide/codebench/codebench_screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kohana/codebench/3bde69a90e8761c797608aeb33a504f24dcd4ae4/media/guide/codebench/codebench_screenshot2.png -------------------------------------------------------------------------------- /views/codebench.php: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | <?php if ($class !== ''): ?> 18 | <?php echo $class, ' · ' ?> 19 | <?php endif; ?>Codebench 20 | 21 | 78 | 79 | 80 | 152 | 153 | 154 | 155 | 156 | 157 | 158 |
159 |

160 | 161 | 162 | 163 | 164 | Library not found 165 | 166 | No methods found to benchmark 167 | 168 | 169 |

170 |
171 | 172 | 173 | 174 | 175 | 176 |

177 | 178 | Remember to prefix the methods you want to benchmark with “bench”.
179 | You might also want to overwrite Codebench->method_filter(). 180 |
181 |

182 | 183 | 184 | 185 |
    186 | $benchmark) { ?> 187 |
  • 188 | 189 |

    190 | 191 | 192 | +% 193 | 194 |

    195 | 196 |
    197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | $subject) { ?> 209 | 210 | 217 | 224 | 231 | 232 | 233 | 234 | 235 |
    Benchmarks per subject for
    subject → returns
    211 | 212 | [] → 213 | 214 | () 215 | 216 | 218 | 219 | 220 | 221 | 222 | 223 | 225 | 226 | 227 | s 228 | 229 | 230 |
    236 |
    237 | 238 |
  • 239 | 240 |
241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | Raw output:', Debug::vars($codebench) ?> 249 | 250 | 251 | 252 | 258 | 259 | 260 | 261 | --------------------------------------------------------------------------------