In statically typed languages, such as C++, properties of objects can be accessed via offset, which is determined at compile time. However, PHP is dynamically typed. There’s no way we can determine which type a
zval is until we execute to the exact line of opcode, so it’s impractical trying to access properties via offset.
In the Zend implementation of PHP, properties of an object is stored in a
zend_array, aka a hashtable. See the definition of
zobj->properties are key-value pairs of property names and values. Whenever a property of a
zend_object is being accessed, the property name is applied to the hash function.
Well, it’s the place where values of default properties are stored, and the closest thing we have to fetching properties by offset. Let’s suppose there’s a userland PHP class defined as below.
$second property of class
Pair are defined explicitly in the code, thus, they are considered as default properties. Value of
$first is stored in
zobj->properties_table, which is determined at compile time. So you can see that the number of default properties affect the size of
However, that doesn’t mean that those properties can be accessed via offset. Consider the following code:
After a short tour of the PHP source code or some debugging with GDB, it will dawn upon you, that even the offset of a default property can be determined at compile time in theory, the Zend engine doesn’t do the optimization, not even in the incoming PHP 7.3 (You can RFC it if you like, but believe me, that could be quite a nasty job).
“Wait a sec. I didn’t see a hashtable holding the names of those default properties.”
Of course not. That hashtable is defined in the class entry, whose attributes are determined at compile time.
Whenever you’re trying to access a property, it first search the
zobj->ce->properties_info, whose buckets are key-value pairs of default property names and their offsets. If the key does not exist, search
zobj->properties. See the source code for details. Behaviors on
zend_objects are controlled by
zobj->handlers. Unless overriden by native code, those std handler functions will always get called.
Having to perform a second hash search makes accessing dynamic property slower. Meanwhile, as
NULL by default, allocating and initializing a
zend_array as container for dynamic properties is also expensive.
For performance comparison, try the following benchmark.
For the first benchmark, it’s about 32% faster when using a
Pair with default properties than one without. For the second one, it’s 41%. (Using PHP 7.2.9 ZTS DEBUG on Darwin, and similar results for other builds of PHP)
Well, in userland PHP, there’s nothing more we can do. Just bear in mind that dynamic properties work poorly in performance. Always define properties explicitly if you can.
However, in native context (e.g. in a PHP extension), performance can be improved drastically, as there’s no need for hashtables when accessing default properties.
With Zend API
zend_declare_property() and its variants, we can declare default properties in
PHP_MINIT_FUNCTION, which will be called only once when the extension gets loaded. Then we can access the values of those properties by
zobj->properties_tables[offset]. It’s recommended that you just use macro
See the following example for a