diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..496ee2c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.DS_Store
\ No newline at end of file
diff --git a/classes/arrcallback.php b/classes/arrcallback.php
new file mode 100644
index 0000000..5d9dbb3
--- /dev/null
+++ b/classes/arrcallback.php
@@ -0,0 +1,55 @@
+
+ */
+class Arrcallback extends Codebench {
+
+ public $description =
+ 'Parsing "command[param,param]" strings in arr::callback():
+ http://github.com/shadowhand/kohana/commit/c3aaae849164bf92a486e29e736a265b350cb4da#L0R127';
+
+ public $loops = 10000;
+
+ public $subjects = array
+ (
+ // Valid callback strings
+ 'foo',
+ 'foo::bar',
+ 'foo[apple,orange]',
+ 'foo::bar[apple,orange]',
+ '[apple,orange]', // no command, only params
+ 'foo[[apple],[orange]]', // params with brackets inside
+
+ // Invalid callback strings
+ 'foo[apple,orange', // no closing bracket
+ );
+
+ public function bench_shadowhand($subject)
+ {
+ // The original regex we're trying to optimize
+ if (preg_match('/([^\[]*+)\[(.*)\]/', $subject, $match))
+ return $match;
+ }
+
+ public function bench_geert_regex_1($subject)
+ {
+ // Added ^ and $ around the whole pattern
+ if (preg_match('/^([^\[]*+)\[(.*)\]$/', $subject, $matches))
+ return $matches;
+ }
+
+ public function bench_geert_regex_2($subject)
+ {
+ // A rather experimental approach using \K which requires PCRE 7.2 ~ PHP 5.2.4
+ // Note: $matches[0] = params, $matches[1] = command
+ if (preg_match('/^([^\[]*+)\[\K.*(?=\]$)/', $subject, $matches))
+ return $matches;
+ }
+
+ public function bench_geert_str($subject)
+ {
+ // A native string function approach which beats all the regexes
+ if (strpos($subject, '[') !== FALSE AND substr($subject, -1) === ']')
+ return explode('[', substr($subject, 0, -1), 2);
+ }
+}
diff --git a/classes/codebench.php b/classes/codebench.php
new file mode 100644
index 0000000..0f4c55c
--- /dev/null
+++ b/classes/codebench.php
@@ -0,0 +1,3 @@
+request->redirect('codebench/'.strtolower(trim($_POST['class'])));
+
+ // Pass the class name on to the view
+ $this->template->class = $class = ucfirst(trim($class));
+
+ // Try to load the class, then run it
+ if (Kohana::auto_load($class) === TRUE)
+ {
+ $codebench = new $class;
+ $this->template->codebench = $codebench->run();
+ }
+ }
+}
diff --git a/classes/kohana/codebench.php b/classes/kohana/codebench.php
new file mode 100644
index 0000000..6c9b5f5
--- /dev/null
+++ b/classes/kohana/codebench.php
@@ -0,0 +1,221 @@
+ 'A',
+ 150 => 'B',
+ 200 => 'C',
+ 300 => 'D',
+ 500 => 'E',
+ 'default' => 'F',
+ );
+
+ /**
+ * Constructor.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ // Set the maximum execution time
+ set_time_limit(Kohana::config('codebench')->max_execution_time);
+ }
+
+ /**
+ * Runs Codebench on the extending class.
+ *
+ * @return array benchmark output
+ */
+ public function run()
+ {
+ // Array of all methods to loop over
+ $methods = array_filter(get_class_methods($this), array($this, 'method_filter'));
+
+ // Make sure the benchmark runs at least once,
+ // also if no subject data has been provided.
+ if (empty($this->subjects))
+ {
+ $this->subjects = array('NULL' => NULL);
+ }
+
+ // Initialize benchmark output
+ $codebench = array
+ (
+ 'class' => get_class($this),
+ 'description' => $this->description,
+ 'loops' => array
+ (
+ 'base' => (int) $this->loops,
+ 'total' => (int) $this->loops * count($this->subjects) * count($methods),
+ ),
+ 'subjects' => $this->subjects,
+ 'benchmarks' => array(),
+ );
+
+ // Benchmark each method
+ foreach ($methods as $method)
+ {
+ // Initialize benchmark output for this method
+ $codebench['benchmarks'][$method] = array('time' => 0, 'memory' => 0);
+
+ // Using Reflection because simply calling $this->$method($subject) in the loop below
+ // results in buggy benchmark times correlating to the length of the method name.
+ $reflection = new ReflectionMethod(get_class($this), $method);
+
+ // Benchmark each subject on each method
+ foreach ($this->subjects as $subject_key => $subject)
+ {
+ // Prerun each method/subject combo before the actual benchmark loop.
+ // This way relatively expensive initial processes won't be benchmarked, e.g. autoloading.
+ // At the same time we capture the return here so we don't have to do that in the loop anymore.
+ $return = $reflection->invoke($this, $subject);
+
+ // Start the timer for one subject
+ $token = Profiler::start('codebench', $method.$subject_key);
+
+ // The heavy work
+ for ($i = 0; $i < $this->loops; ++$i)
+ {
+ $reflection->invoke($this, $subject);
+ }
+
+ // Stop and read the timer
+ Profiler::stop($token);
+ $benchmark = Profiler::total($token);
+
+ // Temporary ugly workaround
+ $benchmark['time'] = $benchmark[0];
+ $benchmark['memory'] = $benchmark[1];
+
+ // Benchmark output specific to the current method and subject
+ $codebench['benchmarks'][$method]['subjects'][$subject_key] = array
+ (
+ 'return' => $return,
+ 'time' => $benchmark['time'],
+ 'memory' => $benchmark['memory'],
+ );
+
+ // Update method totals
+ $codebench['benchmarks'][$method]['time'] += $benchmark['time'];
+ $codebench['benchmarks'][$method]['memory'] += $benchmark['memory'];
+ }
+ }
+
+ // Initialize the fastest and slowest benchmarks for both methods and subjects, time and memory,
+ // these values will be overwritten using min() and max() later on.
+ // The 999999999 values look like a hack, I know, but they work,
+ // unless your method runs for more than 31 years or consumes over 1GB of memory.
+ $fastest_method = $fastest_subject = array('time' => 999999999, 'memory' => 999999999);
+ $slowest_method = $slowest_subject = array('time' => 0, 'memory' => 0);
+
+ // Find the fastest and slowest benchmarks, needed for the percentage calculations
+ foreach ($methods as $method)
+ {
+ // Update the fastest and slowest method benchmarks
+ $fastest_method['time'] = min($fastest_method['time'], $codebench['benchmarks'][$method]['time']);
+ $fastest_method['memory'] = min($fastest_method['memory'], $codebench['benchmarks'][$method]['memory']);
+ $slowest_method['time'] = max($slowest_method['time'], $codebench['benchmarks'][$method]['time']);
+ $slowest_method['memory'] = max($slowest_method['memory'], $codebench['benchmarks'][$method]['memory']);
+
+ foreach ($this->subjects as $subject_key => $subject)
+ {
+ // Update the fastest and slowest subject benchmarks
+ $fastest_subject['time'] = min($fastest_subject['time'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['time']);
+ $fastest_subject['memory'] = min($fastest_subject['memory'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['memory']);
+ $slowest_subject['time'] = max($slowest_subject['time'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['time']);
+ $slowest_subject['memory'] = max($slowest_subject['memory'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['memory']);
+ }
+ }
+
+ // Percentage calculations for methods
+ foreach ($codebench['benchmarks'] as & $method)
+ {
+ // Calculate percentage difference relative to fastest and slowest methods
+ $method['percent']['fastest']['time'] = $method['time'] / $fastest_method['time'] * 100;
+ $method['percent']['fastest']['memory'] = $method['memory'] / $fastest_method['memory'] * 100;
+ $method['percent']['slowest']['time'] = $method['time'] / $slowest_method['time'] * 100;
+ $method['percent']['slowest']['memory'] = $method['memory'] / $slowest_method['memory'] * 100;
+
+ // Assign a grade for time and memory to each method
+ $method['grade']['time'] = $this->grade($method['percent']['fastest']['time']);
+ $method['grade']['memory'] = $this->grade($method['percent']['fastest']['memory']);
+
+ // Percentage calculations for subjects
+ foreach ($method['subjects'] as & $subject)
+ {
+ // Calculate percentage difference relative to fastest and slowest subjects for this method
+ $subject['percent']['fastest']['time'] = $subject['time'] / $fastest_subject['time'] * 100;
+ $subject['percent']['fastest']['memory'] = $subject['memory'] / $fastest_subject['memory'] * 100;
+ $subject['percent']['slowest']['time'] = $subject['time'] / $slowest_subject['time'] * 100;
+ $subject['percent']['slowest']['memory'] = $subject['memory'] / $slowest_subject['memory'] * 100;
+
+ // Assign a grade letter for time and memory to each subject
+ $subject['grade']['time'] = $this->grade($subject['percent']['fastest']['time']);
+ $subject['grade']['memory'] = $this->grade($subject['percent']['fastest']['memory']);
+ }
+ }
+
+ return $codebench;
+ }
+
+ /**
+ * Callback for array_filter().
+ * Filters out all methods not to benchmark.
+ *
+ * @param string method name
+ * @return boolean
+ */
+ protected function method_filter($method)
+ {
+ // Only benchmark methods with the "bench" prefix
+ return (substr($method, 0, 5) === 'bench');
+ }
+
+ /**
+ * Returns the applicable grade letter for a score.
+ *
+ * @param integer|double score
+ * @return string grade letter
+ */
+ protected function grade($score)
+ {
+ foreach ($this->grades as $max => $grade)
+ {
+ if ($max === 'default')
+ continue;
+
+ if ($score <= $max)
+ return $grade;
+ }
+
+ return $this->grades['default'];
+ }
+}
diff --git a/classes/ltrimdigits.php b/classes/ltrimdigits.php
new file mode 100644
index 0000000..b080c37
--- /dev/null
+++ b/classes/ltrimdigits.php
@@ -0,0 +1,26 @@
+
+ */
+class Ltrimdigits extends Codebench {
+
+ public $description = 'Chopping off leading digits: regex vs ltrim.';
+
+ public $loops = 100000;
+
+ public $subjects = array
+ (
+ '123digits',
+ 'no-digits',
+ );
+
+ public function bench_regex($subject)
+ {
+ return preg_replace('/^\d+/', '', $subject);
+ }
+
+ public function bench_ltrim($subject)
+ {
+ return ltrim($subject, '0..9');
+ }
+}
diff --git a/classes/validcolor.php b/classes/validcolor.php
new file mode 100644
index 0000000..494190f
--- /dev/null
+++ b/classes/validcolor.php
@@ -0,0 +1,114 @@
+
+ */
+class ValidColor extends Codebench {
+
+ public $description =
+ 'Optimization for Validate::color()
.
+ See: http://forum.kohanaphp.com/comments.php?DiscussionID=2192.
+
+ Note that the methods with an _invalid suffix contain flawed regexes and should be
+ completely discarded. I left them in here for educational purposes, and to remind myself
+ to think harder and test more thoroughly. It can\'t be that I only found out so late in
+ the game. For the regex explanation have a look at the forum topic mentioned earlier.';
+
+ public $loops = 10000;
+
+ public $subjects = array
+ (
+ // Valid colors
+ 'aaA',
+ '123',
+ '000000',
+ '#123456',
+ '#abcdef',
+
+ // Invalid colors
+ 'ggg',
+ '1234',
+ '#1234567',
+ "#000\n",
+ '}§è!çà%$z',
+ );
+
+ // Note that I added the D modifier to corey's regexes. We need to match exactly
+ // the same if we want the benchmarks to be of any value.
+ public function bench_corey_regex_1_invalid($subject)
+ {
+ return (bool) preg_match('/^#?([0-9a-f]{1,2}){3}$/iD', $subject);
+ }
+
+ public function bench_corey_regex_2($subject)
+ {
+ return (bool) preg_match('/^#?([0-9a-f]){3}(([0-9a-f]){3})?$/iD', $subject);
+ }
+
+ // Optimized corey_regex_1
+ // Using non-capturing parentheses and a possessive interval
+ public function bench_geert_regex_1a_invalid($subject)
+ {
+ return (bool) preg_match('/^#?(?:[0-9a-f]{1,2}+){3}$/iD', $subject);
+ }
+
+ // Optimized corey_regex_2
+ // Removed useless parentheses, made the remaining ones non-capturing
+ public function bench_geert_regex_2a($subject)
+ {
+ return (bool) preg_match('/^#?[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $subject);
+ }
+
+ // Optimized geert_regex_1a
+ // Possessive "#"
+ public function bench_geert_regex_1b_invalid($subject)
+ {
+ return (bool) preg_match('/^#?+(?:[0-9a-f]{1,2}+){3}$/iD', $subject);
+ }
+
+ // Optimized geert_regex_2a
+ // Possessive "#"
+ public function bench_geert_regex_2b($subject)
+ {
+ return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $subject);
+ }
+
+ // Using \z instead of $
+ public function bench_salathe_regex_1($subject)
+ {
+ return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?\z/i', $subject);
+ }
+
+ // Using \A instead of ^
+ public function bench_salathe_regex_2($subject)
+ {
+ return (bool) preg_match('/\A#?+[0-9a-f]{3}(?:[0-9a-f]{3})?\z/i', $subject);
+ }
+
+ // A solution without regex
+ public function bench_geert_str($subject)
+ {
+ if ($subject[0] === '#')
+ {
+ $subject = substr($subject, 1);
+ }
+
+ $strlen = strlen($subject);
+ return (($strlen === 3 OR $strlen === 6) AND ctype_xdigit($subject));
+ }
+
+ // An ugly, but fast, solution without regex
+ public function bench_salathe_str($subject)
+ {
+ if ($subject[0] === '#')
+ {
+ $subject = substr($subject, 1);
+ }
+
+ // TRUE if:
+ // 1. $subject is 6 or 3 chars long
+ // 2. $subject contains only hexadecimal digits
+ return (((isset($subject[5]) AND ! isset($subject[6])) OR
+ (isset($subject[2]) AND ! isset($subject[3])))
+ AND ctype_xdigit($subject));
+ }
+}
diff --git a/config/codebench.php b/config/codebench.php
new file mode 100644
index 0000000..2776b46
--- /dev/null
+++ b/config/codebench.php
@@ -0,0 +1,16 @@
+ 0,
+
+ /**
+ * Expand all benchmark details by default.
+ */
+ 'expand_all' => FALSE,
+
+);
diff --git a/init.php b/init.php
new file mode 100644
index 0000000..866cc34
--- /dev/null
+++ b/init.php
@@ -0,0 +1,8 @@
+)')
+ ->defaults(array(
+ 'controller' => 'codebench',
+ 'action' => 'index',
+ 'class' => NULL));
diff --git a/views/codebench.php b/views/codebench.php
new file mode 100644
index 0000000..56170d7
--- /dev/null
+++ b/views/codebench.php
@@ -0,0 +1,256 @@
+
+
+
+
+
+
+ Remember to prefix the methods you want to benchmark with “bench”.
+ You might also want to overwrite Codebench->method_filter()
.
+
+
subject → return | ++ | s | +
---|---|---|
+ + [] → + + () + + | ++ + + + + + | ++ + + s + + + | +