PHP performance test - functional, or OOP comparison

chris (2008-10-12 00:00:19)
3078 views
3 replies
This article aimed to prove that calling methods in PHP directly from a function library rather than through a helper class offers superior performance and simpler code. I secretly get irritated by PHP developers who can't cope with procedural programming. I see it mostly in junior coders who have only done Java at school/university and don't have a background in C or any of the functional programming languages. They can't even write a simple hello world without having to create a helloWorld.php class file. If a class doesn't describe something which is inherently object-like, it just looks wrong to me.

So as for the first part of this experiment, I created a simple function library called testCache.php. It's contents look like this:

<?php
// php functions to store and fetch to/from apc cache
     function fetch($key){
          return apc_fetch($key);
     }
 
     function store($key, $data, $ttl){
          return apc_store($key, $data, $ttl);
     }

?>

And then I wrote a simple script to iteratively load up the user cache with values, re-testing their values and retrieving or re-populating depending on the cache state:

<?php
// php test script to load/retrieve from cache in apc
include '/export/www/testCache.php';

for($i=0; $i<200; $i++){
	$j=2*$i;
	if(!fetch($i)){
		store($i,$j,5);
	}
}
?>

The second version of this experiment takes an object oriented (OO) approach. In this version, I have used a helper class with two public methods (store and fetch). This approach doesn't explicitly instantiate a testCache object, but statically calls its methods.

<?php
// CacheManager class to allow fetching/storage to/from apc cache
class CacheManager{
     public function fetch($key){
          return apc_fetch($key);
     }
 
     public function store($key, $data, $ttl){
          return apc_store($key, $data, $ttl);
     }
}
?>

And this time the test code is modified to use the somewhat oddly-named 'scope resolution operator' - allowing us to call those methods without instantiating the CacheManager class. Of course I ensured that the cache was flushed before any of these load tests were conducted.

<?php
// Test code to run under load against CacheManager store/fetch methods
include '/export/www/testCache.class.php';

for($i=0; $i<200; $i++){
	$j=2*$i;
	if(!CacheManager::fetch($i)){
		CacheManager::store($i,$j,5);
	}
}
?>

I also created a third version of this test. Again taking the OO approach, This test instantiates a testCache object from the testCache class and calls the methods within it.

<?php
// Test code to run under load against CacheManager store/fetch methods
class CacheManager{
     private function fetch($key){
          return apc_fetch($key);
     }
 
     private function store($key, $data, $ttl){
          return apc_store($key, $data, $ttl);
     }
}
?>

In order to load these different versions of the apc storage/retrieval code, I used the seige utility, calling the script with a statement like:
$ siege -c 10 "http://localhost/cachetest.php" -b -t300s > /tmp/seige.log  

That would load the url, holding 10 concurrent connections for a period of 300 seconds. The results (in order as above) were as follows:

Results 1
Transactions:		       17357 hits
Availability:		      100.00 %
Elapsed time:		      299.99 secs
Data transferred:	        0.00 MB
Response time:		        0.17 secs
Transaction rate:	       57.86 trans/sec
Throughput:		        0.00 MB/sec
Concurrency:		        9.99
Successful transactions:       17357
Failed transactions:	           0
Longest transaction:	        0.36
Shortest transaction:	        0.01


Results 2
Transactions:		       15476 hits
Availability:		      100.00 %
Elapsed time:		      299.55 secs
Data transferred:	        0.00 MB
Response time:		        0.19 secs
Transaction rate:	       51.66 trans/sec
Throughput:		        0.00 MB/sec
Concurrency:		        9.99
Successful transactions:       15476
Failed transactions:	           0
Longest transaction:	        0.37
Shortest transaction:	        0.01


Results 3
Transactions:		       75326 hits
Availability:		      100.00 %
Elapsed time:		      299.76 secs
Data transferred:	        0.00 MB
Response time:		        0.04 secs
Transaction rate:	      251.29 trans/sec
Throughput:		        0.00 MB/sec
Concurrency:		        9.96
Successful transactions:       75326
Failed transactions:	           0
Longest transaction:	        0.94
Shortest transaction:	        0.00

Well - I'm very surprised to see the full-blown object-oriented approach to be performing so well. I would have thought the continuous object instantiation (which is known to be an expensive operation) would slow that third test down. I have since tried this with apc completely disabled and used a simple array-based cache storage approach instead. Strangely, the results showed even better performance still from the OOP approach. I'll have to figure out why.

christo
comment
Jose Fonseca
2008-10-13 10:18:43

I was finding it really hard to believe :p

Hi Chris

I think there is a fundamental problem with the tests because different tests are not running in the same conditions.

Basically, if you want to measure the performance of procedural vs oo, you need to need to make sure that all tests are executed with the exact same conditions. You can do this in different ways, for example, you can remove the check to see if the data is already cached because not actually trying to measure the performance of the APC cache itself, but the difference between the OO and the procedural approach.

If you do this, you can see that the procedural code consistently gives you better results. On average I get a 10% improvement.

Cheers,
José
reply iconedit reply
Jose Fonseca
2008-10-13 10:21:00

By the way

I think that if you change the order in which of the tests (test the OO first and the procedural second), you'll find that the the procedural is faster because there is a bigger chance that the values are already in cache from the previous test ;)

-
José
reply iconedit reply
chris
2008-10-13 23:09:28

I think that if you change the order in which of the tests (test the OO first and the procedural second), you'll find that the the procedural is faster because there is a bigger chance that the values are already in cache from the previous test ;)


All entries were removed from the user cache before running each test.

christo
reply icon