Table of Contents

Run Time Cache

This RFC offers an implementation of run-time caching technique which may improve performance of repeatable code

Introduction

During execution PHP often access different HashTables to get functions, constants, methods and properties by name. In case we save the result of lookup associated with current opcode we can quickly reuse the same value without HashTable access. This technique is called “inline caching” (See http://en.wikipedia.org/wiki/Inline_caching for more details).

Implementation

For each opcode that performs run-time binding of a constant name PHP compiler allocates a cache slot and stores its number in the zend_literal.cache_slot. The cache itself (zend_op_array.run_time_cache) is allocated on the first execution of op_array. During execution PHP checks if cache with this slot was initialized before and omits unnecessary lookups. PHP uses CACHED_PTR(slot) macro to check if it already performed a lookup before for constants, functions and classes and CACHED_POLYMORPHIC_PTR(slot, class_entry) for methods and properties which may be different for different classes. In the second case the cache keeps not only a pointer to method or property info but also the class entry which were used on previous lookup and checks if it's the same.

To utilize this technique for properties access the structure of zend_class_entry and zend_object were changed a bit. Instead of keeping properties in HashTables now we store them in plain arrays and access them by offset (zend_property_info.offset) calculated at compile time (or during inheritance). In case object uses dynamic properties (non declared properties) or requires access through HashTable the plain array is converted to HashTable. This optimization is important itself because management of plain array significantly cheaper.

A lot of changes in the patch caused by modification of zend_class_entry/zend_object structures.

Performance Evaluation

The patch makes more than 20% speed up on synthetic benchmark.

php-trunk patched improvement
bench.php (sec) 3.47 3.45 1%
micro_bench.php (sec) 15.35 11.90 22%

It also makes visible speedup on real-life object oriented applications.

(the measurement was done with modified version of Zend Optimizer+)

php-trunk patched improvement
blog (req/sec) 66 68.2 3%
drupal (req/sec) 1073.7 1074.3 0%
fw (req/sec) 109.8 113.4 3%
hello (req/sec) 5791.79 5823.98 1%
qdig (req/sec) 251 252.3 1%
typo3 (req/sec) 378.7 382.2 1%
wordpress (req/sec) 107.8 108.9 1%
xoops (req/sec) 79.2 84.1 6%
scrum (req/sec) 105.9 114.7 8%

Patch

Notes for Extension Maintainers

As was said before the patch modifies zend_class_entry and zend_object structure, so in case the extension works with properties directly it should be modified a bit. The key changes which may affect the extensions are in zend.h

Index: Zend/zend.h
===================================================================
--- Zend/zend.h	(revision 299688)
+++ Zend/zend.h	(working copy)
@@ -298,6 +298,7 @@
 typedef struct _zend_object {
 	zend_class_entry *ce;
 	HashTable *properties;
+	zval **properties_table;
 	HashTable *guards; /* protects from __get/__set ... recursion */
 } zend_object;
 
@@ -468,11 +469,13 @@
 	zend_uint ce_flags;
 
 	HashTable function_table;
-	HashTable default_properties;
 	HashTable properties_info;
-	HashTable default_static_members;
-	HashTable *static_members;
+	zval **default_properties_table;
+	zval **default_static_members_table;
+	zval **static_members_table;
 	HashTable constants_table;
+	int default_properties_count;
+	int default_static_members_count;
 	const struct _zend_function_entry *builtin_functions;
 
 	union _zend_function *constructor;

For example property initialization in class constructors should be done using a special function instead of direct HashTable copying. Such copying isn't possible any more because default_properties HashTable is replaced with plain array - default_properties_table.

Index: ext/xsl/php_xsl.c
===================================================================
--- ext/xsl/php_xsl.c	(revision 299688)
+++ ext/xsl/php_xsl.c	(working copy)
@@ -129,7 +128,7 @@
 	intern->profiling = NULL;
 
 	zend_object_std_init(&intern->std, class_type TSRMLS_CC);
-	zend_hash_copy(intern->std.properties, &class_type->default_properties, (copy_ctor_func_t) zval_add_ref, (void *) &tmp, sizeof(zval *));
+	object_properties_init(&intern->std, class_type);
 	ALLOC_HASHTABLE(intern->parameter);
 	zend_hash_init(intern->parameter, 0, NULL, ZVAL_PTR_DTOR, 0);
 	ALLOC_HASHTABLE(intern->registered_phpfunctions);

In case extension accesses object properties in HashTable it has to check if such HashTable initialized and rebuld it if necessary.

Index: ext/spl/spl_dllist.c
===================================================================
--- ext/spl/spl_dllist.c	(revision 299688)
+++ ext/spl/spl_dllist.c	(working copy)
@@ -523,6 +522,9 @@
 		INIT_PZVAL(&zrv);
 		Z_ARRVAL(zrv) = intern->debug_info;
 
+		if (!intern->std.properties) {
+			rebuild_object_properties(&intern->std);
+		}
 		zend_hash_copy(intern->debug_info, intern->std.properties, (copy_ctor_func_t) zval_add_ref, (void *) &tmp, sizeof(zval *));
 
 		pnstr = spl_gen_private_prop_name(spl_ce_SplDoublyLinkedList, "flags", sizeof("flags")-1, &pnlen TSRMLS_CC);