PHP: Objekte schneller erzeugen mit Caching
Gestern habe ich etwas über "static" Variablen in Funktionen geschrieben, heute kommt gleich eine praktische Anwendungsmöglichkeit.
Wer in seinen Scripten/Programmen oft viele Objekte erzeugen muss, der könnte dies locker 10% bis 30% schneller machen indem man fertige Objekte einfach cacht.
Wie das geht und was es bringt, einfach weiterlesen.
Zum testen habe ich das Smarty Template Framework genommen, eine relativ große Klasse die auch gerne mehrmals in einem Aufruf erzeugt werden muss. Das Gegenstück ist eine sehr kleine Klasse die den reinen Geschwindigkeitsvorteil demonstrieren soll wenn man ein Objekt nicht neu erzeugen muss.
Vorweg schon mal die Ergebnisse, das Benchmark-Script kommt direkt danach:
18.325 seconds - Smarty NON cached
11.786 seconds - Smarty cached
3.337 seconds - Tiny NON cached
3.071 seconds - Tiny cached
Smarty is 35.68% faster with caching
Tiny is 7.98% faster with caching
Das Script:
/**
* Benchmark Script for checking object caching speed improvements.
* A tiny class and the huge smarty class is used to check
* how class size differs speed.
*
* Caching of of objects is done by functions where these objects are
* cached in static vars. Only a clone of this objects is returned.
*/
/**
* Gettings classes
*/
// Require smarty class
require_once('Smarty-2.6.26\libs\Smarty.class.php');
// Our tiny test Class
class Tiny {
function __construct() {
// Doing nothing
}
function __destruct() {
// Doing nothing
}
function calc_something($i=1) {
return ($i < 1) ? 1 : $i+$this->calc_something(--$i);
}
}
/**
* Caching functions
*/
// Smarty caching function
function smarty_instance() {
static $smarty_template = false;
// Create a new object
if(!$smarty_template) {
if(class_exists('Smarty')) {
$smarty_template = new Smarty();
$smarty_template->template_dir = '/var/www/smarty_template';
$smarty_template->compile_dir = '/tmp/smarty_compile';
}
}
return clone $smarty_template;
}
// Tiny caching function
function tiny_instance() {
static $tiny_template = false;
// Create a new object
if(!$tiny_template) {
if(class_exists('Tiny')) {
$tiny_template = new Tiny();
}
}
return clone $tiny_template;
}
// How much objects should be created?
$n = 1000000;
/**
* Benchmark
*/
echo "Benchmarking $n times creating a Object:\n";
// Creating $n times Smarty with "new"
$time_start = microtime(true);
$i = -1;
while(++$i < $n) {
$devnull = new Smarty();
$devnull->template_dir = '/var/www/smarty_template';
$devnull->compile_dir = '/tmp/smarty_compile';
}
$time_end = microtime(true);
$time_smarty_non_cached = $time_end-$time_start;
echo number_format($time_smarty_non_cached, 3, '.', '')
." seconds - Smarty NON cached\n";
// Creating $n times Smarty with "smarty_instance()"
$time_start = microtime(true);
$i = -1;
while(++$i < $n) {
$devnull = smarty_instance();
}
$time_end = microtime(true);
$time_smarty_cached = $time_end-$time_start;
echo number_format($time_smarty_cached, 3, '.', '')
." seconds - Smarty cached\n";
// Creating $n times Tiny with "new"
$time_start = microtime(true);
$i = -1;
while(++$i < $n) {
$devnull = new Tiny();
}
$time_end = microtime(true);
$time_tiny_non_cached = $time_end-$time_start;
echo number_format($time_tiny_non_cached, 3, '.', '')
." seconds - Tiny NON cached\n";
// Creating $n times Tiny with "tiny_instance()"
$time_start = microtime(true);
$i = -1;
while(++$i < $n) {
$devnull = tiny_instance();
}
$time_end = microtime(true);
$time_tiny_cached = $time_end-$time_start;
echo number_format($time_tiny_cached, 3, '.', '')
." seconds - Tiny cached\n";
// Output conclusion
echo "\n";
echo "Smarty is "
.number_format((1-($time_smarty_cached/$time_smarty_non_cached))*100, 2, '.', '')
."% faster with caching\n";
echo "Tiny is "
.number_format((1-($time_tiny_cached/$time_tiny_non_cached))*100, 2, '.', '')
."% faster with caching\n";
?>
Das cachen der Objekte findet in den Funktionen smarty_instance() und tiny_instance() statt. Die Verwendung von Funktionen hat den großen Vorteil dass sie per Default Global verfügbar sind und es daher überall möglich ist sich ein neues Objekt zu erzeugen.
Beim ersten Aufruf der *_instance() Funktionen wird das "Template" Objekt erzeugt welches in der Funktion in einer "static" Variable gespeichert wird. Beim nächsten Aufruf wird einfach mittels "clone" eine Kopie des Objekts zurückgegeben, was, wie die Messungen zeigen, mindestens 8% schneller ist.
Dazu bieten die *_instance() Funktionen noch den Vorteil dass die gecachten Objekte alle korrekt initialisiert werden können da in den Ablauf der Funktion nicht eingegriffen werden kann.
Man könnte diese Idee auch weiter verfolgen und würde zu einer allgemeinen Objekt Caching Funktion kommen. Diese würden dann in etwa so aussehen:
/**
* Function for caching Objects
* @param string name of object to fetch/store
* @param object optional - just if we store a new obj.
* @return object false or copy of object with name from first parameter
*/
function object_cache($name, &$obj=false) {
static $cache = array();
// Cache a new object if not allready cached
if($name && $obj && is_object($obj) && !array_key_exists($name, $cache))
$cache[$name] = $obj;
// Return copy of object or false
return ($name && array_key_exists($name, $cache))
? clone $cache[$name] : false;
}
?>
In diese Funktion könnte man am Anfang eines Scripts seine fertigen Objekt Templates speichern und könnte sich aufgrund des Überschreibschutz sicher sein dass man immer sein korrektes Objekt zurückbekommt.
Ich hoffe ich habe bei meinen Lesern neue Ideen geweckt wo man schneller und effizienter Programmieren kann. Die Idee ist nicht übrigens neu und auch nicht auf PHP beschränkt.
Greatings, Everything dynamic and very positively! :)
Thanks