Performance of micro-template strategies

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…

Very interesting findings!

I am especially interested in the result of optimized external template.

Do you know if there is any side effect of this approach compared with including the template?

You said overhead would be visible when repetition number is small. Do you have any result about this?

I am also surprised that repeatedly including the same PHP file doesn’t get a big performance boost when APC is used.