I used the term "micro-template" when talking about very small templates/views.
Micro templates are useful when rendering very small elements in a standardized way - for example, if various templates need to render a user-link in a consistent way, a very small template for that might look like this:
<a href="/user/<?=$i?>"><?=htmlspecialchars($first.' '.$last)?></a>
I compared three different approaches:
[list=1]
[*]using a flat function
[*]using an external php (template/view) file
[*]wrapping an external template in a dynamically generated function
[/list]
Here’s the benchmark:
<?php
/*
Benchmark to evaluate the performance of micro-template strategies.
Output is as follows:
using built-in function / using external template / using optimized external template
Results without a bytecode cache:
8.368 msec / 145.210 msec / 9.553 msec
Results with Zend accellerator as bytecode cache:
8.345 msec / 85.977 msec / 10.302 msec
Conclusions:
- A native function is only slightly faster than an optimized external template.
- Bytecode caching helps when using the external template strategy, but not significantly.
- There is a huge room for optimization in PHP and/or bytecode caches for external templates.
- When rendering the same template fewer (5-20) times, the optimized strategy results in overhead.
*/
define('NUM_RUNS', 20);
function render_user($id, $first, $last)
{
return '<a href="/user/'.$id.'">'.htmlspecialchars($first.' '.$last).'</a>';
}
$start = microtime(true);
ob_start();
for ($i=0; $i<NUM_RUNS; $i++)
{
$first = 'Rasmus';
$last = 'Schultz';
echo render_user($i,$first,$last);
}
$html = ob_get_clean();
echo number_format(1000 * (microtime(true)-$start), 3) . ' msec / ';
$len = strlen($html);
// external template benchmark:
$start = microtime(true);
ob_start();
for ($i=0; $i<NUM_RUNS; $i++)
{
$first = 'Rasmus';
$last = 'Schultz';
require 'microtemplate-benchmark.tpl.php';
}
$html = ob_get_clean();
echo number_format(1000 * (microtime(true)-$start), 3) . ' msec / ';
if (strlen($html)!==$len) die('fail!');
// optimized external template benchmark:
function render($tpl, $data)
{
static $fn = array();
if (!isset($fn[$tpl]))
{
$fn = create_function('$i,$first,$last', '?>'.file_get_contents($tpl).'<?');
}
call_user_func_array($fn[$tpl], $data);
}
$start = microtime(true);
ob_start();
for ($i=0; $i<NUM_RUNS; $i++)
{
$first = 'Rasmus';
$last = 'Schultz';
render('microtemplate-benchmark.tpl.php', array(
'i' => $i,
'first' => $first,
'last' => $last,
));
}
$html = ob_get_clean();
echo number_format(1000 * (microtime(true)-$start), 3) . ' msec';
if (!strlen($html)===$len) die('fail!');
See the comment in this script for results and conclusions.
The external file, "microtemplate-benchmark.tpl.php", is the first tiny code snippet shown at the top of this post. (no whitespace/newline at the end of that file, or the benchmark will fail)
Basically, I discovered that there is a huge performance overhead when using an external file repeatedly, which is sort of surprising to me. This must not be optimized in PHP at all - it would appear that it’s actually reading/loading/parsing the same code again and again.
I’m thinking of building a micro-template facility of some sort, optimized for tiny views that get reused a lot - it would appear that the normal approach (as used by Yii’s view engine) is not well suited for micro-templating.
I wonder though, if the best approach would be a micro-templating solution, of if an optimized view engine would be a better approach. Many template engines for PHP actually compile each template into a function, and it would appear that this approach yields significantly higher performance if you have many small, often-used views…