├── .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 | '
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.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 | '',
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 | '',
19 | '',
20 | '',
21 | '',
22 | '![Alt text containing [square] brackets](img/install.png)',
23 | '![Empty src]()',
24 |
25 | // Invalid matches
26 | ';
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
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 |
subject → return | 202 |203 | | s | 204 |
---|---|---|
211 | 212 | [] → 213 | 214 | () 215 | 216 | | 217 |218 | 219 | 220 | 221 | 222 | 223 | | 224 |225 | 226 | 227 | s 228 | 229 | 230 | | 231 |