msvrtan's blog

Performance of different PHP class implementations

Introduction


There are few types of class implementations used in PHP and I wanted to see how will they effect performance on such a low level.</p>

Here is a short list of what I was testing:

  1. Class implementing public properties
  2. Class implementing protected properties with usage of magic __get/__set methods to access properties
  3. Class implementing protected properties with usage of magic __get/__set methods to access properties with checking if this property really exists
  4. Class implementing protected properties with usage of getters and setters
  5. Class implementing protected properties (which are not PHP types but simple classes) with usage of getters and setters


All tests supported:</p>

  • X iterations
  • showing memory usage
  • showing peak memory usage
  • showing time used


There were 3 tests generated</p>

  1. Object initialization
  2. Object load
  3. Object load with pushing objects into array instead of destroying them

</p>

Testing


1 Object initialization</p>

Idea here was to iterate 10000 object creation to see if there is any difference in instantiating classes with 1 method vs few methods so later tests would not be polluted.

All tests but the last one were same, and the last one was 5x slower since there was a need to instantiate additional classes as properties inside object.

Test 1: using directly properties

Time spent:        6.2849521636963 ms
Memory start:         262,144 B
Memory end:           262,144 B
Memory peak:          262,144 B

Test 2: using magic __set

Time spent:        6.3259601593018 ms
Memory start:         262,144 B
Memory end:           262,144 B
Memory peak:          262,144 B

Test 3: using magic __set with checking if property exists

Time spent:        6.3290596008301 ms
Memory start:         262,144 B
Memory end:           262,144 B
Memory peak:          262,144 B

Test 4: using implemented setters

Time spent:        6.335973739624 ms
Memory start:         262,144 B
Memory end:           262,144 B
Memory peak:          262,144 B

Test 5: using objects for properties
Time spent:        31.872987747192 ms
Memory start:         262,144 B
Memory end:           262,144 B
Memory peak:          262,144 B

 

2 Object load

Iterating through creation of 10000 objects, after each object was instantiated, load() method was called with array holding 5 property names and values. Each of the property values was filled using respective methods ( directly accessing property or given setters)

Test 1: using directly properties

Time spent:        25.489807128906 ms
Memory start:         262,144 B
Memory end:           262,144 B
Memory peak:          262,144 B

Test 2: using magic __set

Time spent:        86.797952651978 ms
Memory start:         262,144 B
Memory end:           262,144 B
Memory peak:          262,144 B

Test 3: using magic __set with checking if property exists

Time spent:        136.67583465576 ms
Memory start:         262,144 B
Memory end:           262,144 B
Memory peak:          262,144 B

Test 4: using implemented setters

Time spent:        71.701049804688 ms
Memory start:         262,144 B
Memory end:           262,144 B
Memory peak:          262,144 B

Test 5: using objects for properties

Time spent:        146.02208137512 ms
Memory start:         262,144 B
Memory end:           262,144 B
Memory peak:          262,144 B

I was expecting that magic methods or setters would be somewhat slower then directly accessing public properties but this is really too much. More details later.

3 Object load with pushing objects into array instead of destroying them

Testing method was same as test #2 but I added pushing objects into array after loading so I could test how memory is behaving when garbage collection is not able to destroy them.

Test 1: using directly properties

Time spent:        28.702974319458 ms
Memory start:         262,144 B
Memory end:         2,359,296 B
Memory peak:        2,359,296 B

Test 2: using magic __set

Time spent:        89.023113250732 ms
Memory start:         262,144 B
Memory end:         2,359,296 B
Memory peak:        2,359,296 B

Test 3: using magic __set with checking if property exists

Time spent:        147.24612236023 ms
Memory start:         262,144 B
Memory end:         2,359,296 B
Memory peak:        2,359,296 B

Test 4: using implemented setters

Time spent:        75.994968414307 ms
Memory start:         262,144 B
Memory end:         2,359,296 B
Memory peak:        2,359,296 B

Test 5: using objects for properties

Time spent:        155.48205375671 ms
Memory start:         262,144 B
Memory end:         7,340,032 B
Memory peak:        7,340,032 B

First 4 tests are using same amount of memory, but I actually hoped to see some difference since classes are different. Fifth test is expectedly using 3x more memory because of extra 50k simple objects representing object properties.

Consclusion:


If you look into differences on 2nd test for cases #1, #2 and #4 you will see that calling load method on public properties took 19ms, on magic methods 80ms and 65ms on implemented getters and setters. From this I can see that calling a method to set a property value is 200% slower (#1 vs #4 ) while calling a magic method adds addiditonal 23% (#4 vs #2).</p>

I expected magic methods to be somewhat slower but I didn't expect simple setters to be 300% slower then accessing directly properties. So I did one additional test with only calling load method (but load method is doing nothing!) and I got

Time spent:        15.813827514648 ms
Memory start:         262,144 B
Memory end:           262,144 B
Memory peak:          262,144 B

This tells me that 10k of calls to load method is taking 9ms. So setting 50k properties through direct property access takes ~3ms and accessing 50k setter methods takes ~70ms.

I was surprised to see such a big difference (23x times slower!!). Using properties directly might seem like a micro optimization but as PHP has moved towards object oriented paradigm, accessing properties is a very common operation so loosing performance here could make a lot of difference.

In the next post I will cover what was the idea of fifth case, in the mean time  you can see code for this tests on github, feel free to try them (there are bash scripts for easier testing of each one) and feel free to comment.