On the internet, you can find many guides on micro-optimizing PHP, for small applications, these optimizations do not add any value to the application performance. In medium to large applications (depending on the complexity of the processes implemented), the summatory of all that saved overhead could improve the overall application performance.
Early optimization is the root of all evils, most of the time you need to be focused in creating amazing and maintaniable software rather than faster software. The following tips help to write faster PHP code, however if the volume of overhead saved is not considerable the application will perform without a noticeable difference.
Be aware of the difference between code that is compute-bound (slow because it's doing a huge number of instructions) and code that is I/O bound (slow because of disk or network delays).
The following are examples of I/O-bound operations:
- Read/Write on database systems
- Read/Write files on local/external filesystems
- Read/Write to network sockets (tcp/udp/unix sockets/pipes)
- Make HTTP requests from the application to external systems/services
Most of the slowness in a web application is produced by missing or bad optimizations in the I/O bound.
- Complex calculations based on data in memory
- Sorting or data classification
- Full object-oriented programming/abstraction
- High abstracted architectures
Expecting get most of your gains from optimizing compute-bound code, it's usually (though not always) a sign that you're near the end of worthwhile tuning when profiling shows that the bulk of the application's time is spent on network and disk I/O.
We're going to call this strategy, the "read-only" micro-optimization, its goal is reduce the overhead by detecting the parts of your code that are read-only avoiding unnecessary processing.
First, we must to know that one of the great features that PHP provides aiding to reduce memory usage is the copy-on-write capability. Thanks to this, when you make one variable equal to another. ex, $a = $b; this does not mean that $a is duplicating the value of $b. PHP just makes a reference from $a to $b. If you access any of the two variables, actually you're accessing the same value.
In these cases memory is NOT duplicated:
<?php
$a = $b;
$a = $b['foo'];
$a = $b[0][1];
$a = $b->y;
$a = $b->y->x;In these cases memory IS duplicated:
<?php
$a = "" . $b;
$a = $b + 0;
$a = $b - 0;
$a = $b . false;
$a = $b - false;
$a = $b + false;
$a = $b . null;
$a = (string) $b;
$a = (int) $b;In the following code we're going to obtain the robot's name within the 'foreach':
<?php
foreach ($parts as $part) {
echo $robot->getName() . ' ' . $part . '<br>';
}Let's pretend there are 300 parts, this means that $robot->getName() is going to be executed 300 times. PHP does not know that $robot->getName() returns the same value every iteration, so it executes the method 300 times. The developer can easily know this and could "optimize" the code:
<?php
$name = $robot->getName();
foreach ($parts as $part) {
echo $name . ' ' . $part . '<br>';
}The same result is produced, but this version is several times faster.
The following paragraphs explain the process required to execute a function/method/read-property in PHP:
<?php
for ($i = 0; $i < 10; $i++) {
my_func();
}Every function is stored in a global hash in PHP, a hash is a structure optimized to fastly obtain a value based on its key. In this case the key is "my_func", so PHP looks the function body in the global hash from its name.
- Lookup the function body for "my_func"
- Pass any parameters to the function stack, increasing the reference counting (if any)
- Create a symbol table
- The function gets the parameters passed
- Executes the function
- Returns a value (optional)
- Restore parameters from the stack (if any)
- Destroy the symbol table
Inside a cycle (for, while, foreach), the process is performed every time the function is executed.
<?php
for ($i = 0; $i < 10; $i++) {
$a->myFunc();
}- Check that $a is an object
- Get the class definition for the object related to $a
- Convert the name "myFunc" to lowercase: "myfunc"
- Search the method body in the class definitions using the lowercased name
- Check whether in the current scope the method can be called (check visibility: public, protected, private)
- Pass any parameters to the function stack, increasing the reference counting for each parameter (if any)
- Create a symbol table
- The method gets the parameters passed
- Executes the method
- Returns a value (optional)
- Restore parameters from the stack (if any)
- Destroy the symbol table
- Check if an exception has been produced
Inside a cycle (for, while, foreach), the process is performed every time the function is executed.
<?php
for ($i = 0; $i < 10; $i++) {
echo $a->name . ' ' . $i;
}- Check that $a is an object
- Get the class definition for the object related to $a
- Search the property info related to the property
- Check whether in the current scope the property can be read (check visibility: public, protected, private)
Practically nothing, this is the fastest operation
Basically, this strategy consists in replace read-only code by local variables where possible.
This code:
<?php
for ($i = 0; $i < count($names); $i++) {
if ($names[$i] == $user->getName()) {
echo $robot->getProfile()->getName(). ' ' . $user->getName();
} else {
$this->getRobots()->append($names[$i]);
}
}Can be "micro-optimized" to:
<?php
$userName = $user->getName();
$number = count($names);
$profileName = $robot->getProfile()->getName();
$robots = $this->getRobots();
for ($i = 0; $i < $number; $i++) {
$name = $names[$i];
if ($name == $userName) {
echo $profileName . ' ' . $userName;
} else {
$robots->append($name);
}
}Of course, this strategy does not promise great improvements in performance, however, applied to a large code base can improve performance in some important manner.