Skip to content

Instantly share code, notes, and snippets.

@phalcon
Last active December 17, 2015 09:29
Show Gist options
  • Select an option

  • Save phalcon/5587577 to your computer and use it in GitHub Desktop.

Select an option

Save phalcon/5587577 to your computer and use it in GitHub Desktop.

==================================== Yet Another Micro-Optimization Guide

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.

I/O-bound is different from compute-bound

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).

What is I/O-bound?

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.

What is Compute-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.

Read-Only Micro-Optimization

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:

What involves calling a function?

<?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.

What involves calling a method?

<?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.

What involves reading a property?

<?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)

What involves reading a local variable?

Practically nothing, this is the fastest operation

How it works?

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment