2

I have a simple method:

public function validateStringByPrefix(string $string, $prefix)
{
    $valid = false;
    if (is_string($prefix)) {
        if (! empty($prefix) && strpos($string, $prefix) === 0) {
            $valid = true;
        }
    } elseif (is_array($prefix)) {
        foreach ($prefix as $partPrefix) {
            if (! empty($partPrefix) && strpos($string, $partPrefix) === 0) {
                $valid = true;
                break;
            }
        }
    }
    return $valid;
}

After I noticed, that the condition ! empty($prefix) is actually needless, I removed it. I expected a minimal performance increase or at least the same performance as before the change. But instead the performance got worse.

It can only have sense, if there are actually cases with an empty $prefix or $partPrefix. Since the empty(...) check will be very cheap.

But there are no such cases, I've checked this:

if(empty($prefix)) {
    die(__FILE__);
}

before: Webgrind (in percent), with if(! empty(...))

before (with if(! empty(...)))

after: Webgrind (in percent), without if(! empty(...))

after (without if(! empty(...)))

So what can explain such behavior? Why does a needless IF clause, that always fails, increase the performance?


UPDATE

Just took a look into the Webgrind reports in milliseconds:

before: Webgrind (in milliseconds), with if(! empty(...))

before (in milliseconds, with if(! empty(...)))

after: Webgrind (in milliseconds), without if(! empty(...))

after (in milliseconds, without if(! empty(...)))

So counted in milliseconds removing of the needless IF clause increases the performance... How can the result in percent differ from the result in milliseconds?

automatix
  • 14,018
  • 26
  • 105
  • 230
  • 9
    How did you managed to evaluate the performance ? – Anwar Sep 15 '16 at 12:29
  • @Zeratops is correct. – Alive to die - Anant Sep 15 '16 at 12:31
  • Xdebug Profiler + Webgrind. Just added the screenshots for both cases. – automatix Sep 15 '16 at 12:40
  • 2
    Such a small difference could just as easily be the operating system doing some memory cleanup or something. – Siguza Sep 15 '16 at 12:45
  • 2
    You can't measure performance the way that you're trying to do it. A runtime of a program may vary depending on other factors on the system that it is running on, you should at least test this performance some number of times to see if it's averagely better. But other than that, the minimal performance increase you're looking at doesn't really mean anything. – px06 Sep 15 '16 at 12:50
  • @px06 I read that the time displayed (~30seconds) comes from 70k+ runs of the code.... – Martin Sep 15 '16 at 12:53
  • Have you tried debugging the code to see what steps are actually taking place through the runs? – px06 Sep 15 '16 at 13:00
  • Are you even comparing the same code? Your Webgrind screenshots show `validateColumnByPrefix` vs `validateStringByPrefix`. In any case, I agree that this change is so small as to be probably meaningless. – ChrisGPT was on strike Sep 15 '16 at 13:00
  • @px06 It was the percent value. (I've just added the screenshots with time values). But anyway you're right -- a small optimization * 70k calls can be perceptible. – automatix Sep 15 '16 at 13:02
  • @Chris Yes, I'm comparing the performance of the same method, it's only (re-)naming. – automatix Sep 15 '16 at 13:02

2 Answers2

2

There is actually a perfectly logical reason why one function proves marginally faster than the other and it has nothing to do with the fact that you removed or added the empty() condition.

These differences are tiny that they have to be measured in microseconds, but there is in fact a difference. The difference is what order the functions are called in, or more specifically, where they're allocated in the PHP VM's heap.

The differences in performance you're experiencing here can actually vary from system to system. So there should be no expectation of identical results. However, there are some expectations based on a finite amount of system specs. From this we can actually reproduce consistent results to prove why there are just a few microseconds in difference between the two functions.

First take a look at this 3v4l where we define your function with and without the empty() condition as validateStringByPrefix1() and validateStringByPrefix2(), respectively. Here we call on validateStringByPrefix2(), first, which results in what appears to be a 40 microsecond execution time. Note, in both cases the function should return false, and empty($prefix) will never be true (just as you have done in your own test). In the second test with use of empty($prefix) it looks like the function actually performed faster at 11 microseconds.

Second, take a look at this 3v4l where we define the same exactly functions, but call on validateStringByPrefix1() first, and get the opposite results. Now it looks like without use of empty($prefix) the function runs slightly faster at 12 microseconds and the other runs slightly slower at 88 microseconds.

Remember that microtime() is not actually an accurate clock. It can fluctuate slightly within a few microseconds, but typically not enough to be an order of magnitude slower or faster on average. So, yes, there is a difference, but no it's not because of the use of empty() or the lack thereof.

Instead this issue has a lot more to do with how a typical x86 architecture works and how your CPU and memory deal with cache. Function's defined in your code will typically be stored into memory by PHP, in the order that they are first executed (compilation step occurs here). The first function to get executed is going to be cached first. There are concepts of write-through cache, for example, with both write allocate and no write allocate that can effect this. The next function to be executed overwrites that cache, causing a very slight slow down in memory, which may or may not be consistent depending on factors I won't get into here.

However, despite all of these minute differences there really is no faster or slower result despite the use or removal of empty() in this code. These differences are just memory access and allocation trade offs every program suffers from.

This is why when you really really need to micro-optimize a program's code for the fastest possible execution, you typically go through the painstaking process of doing Profile-guided Optimization or PGO.

Optimization techniques based on analysis of the source code alone are based on general ideas as to possible improvements, often applied without much worry over whether or not the code section was going to be executed frequently though also recognising that code within looping statements is worth extra attention.

Sherif
  • 11,786
  • 3
  • 32
  • 57
0

As pointed out in the comments, this changes and results are so small that are meaningless.

In 70k calls, you are getting just 171ms of difference, so this is probably result of some environment change, and not your code. If you run this benchmark 100 more times, the difference will change at every run (and can even increase), as response to changes in the environment.

As "environment" I mean the computer that is running the benckmark, the network where the data is passing, etc. The computer is running other processes than you code, so if another process eats some CPU, your code runs slowly than before, and so on.

To properly benchmark a code, you need to consider lots of thing more than just the code, and even the code can fool you by means of compiler, etc (e.g. take a look at this video. It's JS benchmark, but much of the information can be applied to other programming languages too).

Dinei
  • 4,494
  • 4
  • 36
  • 60