3

Well, not exactly that, but here is an example. Can anyone explain the difference between B and C? How can it be faster to use a magic function to dynamically set a value instead of simply setting the value in the attribute definition?

Here is some code:

[root@vm-202-167-238-17 ~]# cat test.php; for d in A B C; do echo "------"; ./test.php $d; done;
#!/usr/bin/php
<?php

$className = $argv[1];

class A
{
    public function __get($a)
    {
        return 5;
    }
}

class B
{
    public $a = 5;
}

class C
{
    public function __get($a)
    {
        $this->a = 5;

        return 5;
    }
}

$a = new $className;

$start = microtime(true);

for ($i=0; $i < 1000000; $i++)
    $b = $a->a;

$end = microtime(true);

echo (($end - $start) * 1000) ." msec\n";

------
598.90794754028 msec
------
205.48391342163 msec
------
189.7759437561 msec
j0k
  • 22,600
  • 28
  • 79
  • 90
Marc Trudel
  • 1,244
  • 1
  • 12
  • 19
  • i think your benchmark is wrong. try testing in reverse (C,B then A) and doing something more in the loop – The Scrum Meister Dec 21 '10 at 04:21
  • here are the results for C, B A: ------ 182.4688911438 msec , 193.27712059021 msec , 556.14495277405 msec, which means C is still faster than B... – Marc Trudel Dec 21 '10 at 04:24
  • when I reversed, B is faster than C. – El Yobo Dec 21 '10 at 04:28
  • @Yobo: yes, sometimes it seems to switch, but in general it is quite steadily faster to use C... what version of PHP are you using? – Marc Trudel Dec 21 '10 at 04:31
  • you should probably rename the question, as this doesn't appear to be anything to do with the magic methods; the one that uses `__get` is far slower. The real difference seems to be in dynamic properties and declared ones. – El Yobo Dec 21 '10 at 11:06

2 Answers2

4

Magic functions are definitely slower than anything else in PHP, and should be used carefully. This would actually be a good blog subject (auto-creating attributes with magic functions to speed things up... anyway). As El Yobo stated, I modified your PHP script so the tests are more accurate :

<?php

class A {
    public function __get($a) {
        return 5;
    }
}

class B {
    public $a = 5;
}

class C {
    private $a = 5;
    public function __get($a) {
        return $this->a;
    }
}

$classes = array('A','B','C');

header('Content-type: text/plain; charset=utf-8');
foreach ($classes as $className) {
   $a = new $className;

   $start = microtime(true);

   for ($i=0; $i < 1000000; $i++) {
      $b = $a->a;
   }

   $end = microtime(true);

   echo 'Class ' . get_class($a) . ' = ' . (($end - $start) * 1000) ." msec\n";
}

Resulting in

Class A = 378.85212898254 msec
Class B = 109.26413536072 msec
Class C = 423.51794242859 msec

So, there you have it. You can clearly see that magic functions, when used, take about 4 times more to execute than public methods.

** EDIT **

Now, if you dynamically create new class attribute, the magic method will be called only the first time, then any subsequent call will access the dynamically created public attribute (public for backward compatibility). Change class C to :

class C {
    public function __get($a) {
        $this->a = 5;
        return 5;
    }
}

Will output

Class A = 392.09413528442 msec
Class B = 110.16988754272 msec
Class C = 96.771955490112 msec

So this why you say : "Hey! It's faster!" However, look if we reduce the iterations from 1000000 to 10 (for example):

Class A = 0.033140182495117 msec
Class B = 0.0078678131103516 msec
Class C = 0.01215934753418 msec

Class C is now slower than B because it's initial call to the magic method. My best guess would be that PHP handles dynamically created attributes different than declared ones. But after further research, these results may vary depending on OS, CPU arch, memory, PHP version, etc. Therefore these results cannot be taken for granted, and, generally speaking, magic methods will always take longer to execute than using declared public attributes or calling declared public methods.

** EDIT 2 **

Here's the class D test, skipping the magic method whatsoever with dynamic attribute creation :

class D {
   public function __construct() {
      $this->a = 5;
   }
}

Yields these results for 1000 iterations :

Class A = 1.3999938964844 msec
Class B = 0.42200088500977 msec
Class C = 0.3960132598877 msec
Class D = 0.37002563476562 msec       <-- faster

Let's increase our iterations about to 1'000'000 :

Class A = 380.80310821533 msec
Class B = 109.7559928894 msec
Class C = 91.224908828735 msec        <-- faster ???
Class D = 96.340894699097 msec

If magic methods have a great overhead cost, the real question now is : Why, when accessing a same attribute repeatedly many times, is

public function __get($a) {
   $this->a = 5;
   return 5;
}

faster than

public function __construct() {
   $this->a = 5;
}

when creating and accessing dynamic attributes?

Community
  • 1
  • 1
Yanick Rochon
  • 51,409
  • 25
  • 133
  • 214
  • But thats completely different. You will always end up in calling the __get function, as of in my example, you set the value once, and then never call the __get function back again. The question is, why is this faster than a simple get of an already setted attribute? Plus, you make one script run; if you inverse calls, you will see differences. In my examples, it is 3 script runs, which should make the test more fair for all calls. – Marc Trudel Dec 21 '10 at 04:42
  • @Marc Trudel, then what El Yobo said is your answer; when `$c->a` is called the first time, it invokes the magic method and create a new public attribute (if not defined as private, they are public by default for backward compatibility), then all subsequent calls just return that newly created attribute. Reduce your iteration from `100000` to `10` and you'll notice that C takes more time than B, but less than A. As to why it gets faster with more iterations, I guess it would be how PHP stores dynamically created class attributes in contrast with declared ones... – Yanick Rochon Dec 21 '10 at 04:50
  • @Yanick Rochon Would be interesting to find the "break even" point then... I'll try to see if I can come up with something for that :) – Marc Trudel Dec 21 '10 at 05:03
  • Did some quick tests... seems to be between 1000 and 1500 iterations. Interesting thing, if the specs of the machine are higher, the number of iterations necessary to hit that break even point seems to be lower. But this is not a precise testing... – Marc Trudel Dec 21 '10 at 05:11
  • no, it's not. And I asked on IRC and some guy (TML) has B running faster than all others. The variables must be : OS, CPU architecture, memory, PHP version, etc. In any case my best conclusion is that magic will always be slower than anything else in the long run, generally speaking. – Yanick Rochon Dec 21 '10 at 05:14
  • @Yanick : Can you (or anyone) give me an example where they get those numbers? Honestly, I tried on 4 completely different configurations (PHP 5.2, PHP 5.3, with different extensions, etc.) that I can access from here, and except for some exception, I almost always get C faster than B... – Marc Trudel Dec 21 '10 at 06:17
  • Interesting; I've consistently reproduced Marc's results (C faster than B) on 5.3.3 in both directions, while it was the other way around on 5.2.10 (B faster than C when B runs second, C faster than B when C runs second). – El Yobo Dec 21 '10 at 10:27
  • @Yanick, can you get them to try my simplified version above. It ignores all the magic stuff and focuses on the only difference that seems to be relevant - whether the variable was declared first or not. – El Yobo Dec 21 '10 at 11:21
  • @El Yobo, if you connect to irc.freenode.net and join #php, you will find TML there. He's the one stating that B is faster than C in all cases (my machine has C faster than B). This is why I formulated the idea of perhaps a PHP version variable. – Yanick Rochon Dec 21 '10 at 15:10
2

In the last case (class C), you create a property called "a" the first time the magic method is called; thereafter the magic method won't be called, it will just access the property; this is why it isn't as slow as the first case, class A.

The next question is why is case B slower than case C. I'm guessing that it's happening at the OS level; I reversed the test order so that it tests C B A rather than A B C and suddenly test case C is slower than B, so I'm reasonably certain that the performance win there is outside of PHP; from my reasoning in the first paragraph, it stands to reason that all other things being equal test case C should be fractionally slower than test case B (as there will be one call to the magic method, then all the rest are equal). Note also that (for me anyway) in this example A is always faster than B on 5.2.10 and on 5.3.3.

EDIT

I think that the question title is distracting from interesting issue here; the performance differences (whatever they are) are unrelated to the magic __get method. The following example illustrates this more simply, without bothering about the __get confusing things. Instead we create the dynamic property in the constructor; then the only difference is whether the property is declared first (e.g. public $a) or created dynamically when the value is assigned.

$className = $argv[1];

class A
{
    public function __construct() {
        $this->a = 5;
    }
}

class B extends A
{
    public $a;
}

$a = new $className;

$start = microtime(true);

for ($i=0; $i < 1000000; $i++)
    $b = $a->a;

$end = microtime(true);

echo (($end - $start) * 1000) ." msec\n";
El Yobo
  • 14,823
  • 5
  • 60
  • 78
  • Yeah, I know that C is of course gonna be faster than A :) But what I am wondering is why here, on 3 different systems (Ubuntu, a VPS under CentOS with remi packages and a custom built PHP53 on Centos) I mostly always get a faster result with C than B. For instance, here is another result set: http://pastebin.com/7z7uiGiz – Marc Trudel Dec 21 '10 at 04:34
  • @Marc, as I've pointed out, when I reverse the order, C is slower than B. – El Yobo Dec 21 '10 at 04:43
  • @El Yobo check my pastebin link in the previous comment... but if that is the case, strange. Again, whats your version of PHP, whats your setup? – Marc Trudel Dec 21 '10 at 04:49
  • @Marc, it was on 5.2.10. I have also tried on 5.3 and get the same results as you; case C is always slightly faster. – El Yobo Dec 21 '10 at 10:25
  • @El Yobo, I also tried a class D where the dynamic attribute is created in the constructor, and sometimes a slightly faster than B, but slower than C, but is almost always all the time the fastest (see my "Edit 2") – Yanick Rochon Dec 21 '10 at 15:21