Written on Fri, Feb 27th 2009, 14:41 by Mark Story
Now there has been a lot of discussion in the past few months about requestAction() and how it can very easily create a negative impact on your application. In fact I even wrote such an article myself. However, its high time that someone did the number crunching to really see if
requestAction() is actually as slow as we all seem to think it is. So onto the testing method and the results.
To test this theory I used a small CakePHP application and the SVN head (revision 8064) of CakePHP. I used a simple sample application with 2 controllers and 2 models. My model method directly returned the results without touching the database, so that database retrieval time and model processing would not be a factor in these tests. As I was only interested in the performance implications inherent in
requestAction() itself, I wanted to remove the variance created by connecting to a database. I set debug = 0, and used basic file caching. After warming up the cake core caches, I tested 4 different controller actions.
- Using Relations / ClassRegistry::init() - The method I originally proposed, and often touted as the 'best' solution to requestAction()
- Using RequestAction with a string URL
- Using RequestAction with and Array URL
- Using a cached RequestAction - This more accurately simulates how we use requestAction at CakeDC.
Benchmarks were generated with Siege I used 10 concurrent users with 110 reps each. My local development web-server is running Apache 2.2/PHP 5.2.6 o n a 2.6GHz Core 2 Duo iMac with 2GB of ram. I ran each test 3 times and took the best result of each.
Using model relations / ClassRegistry::init()
First up was my originally proposed solution of using model relations to access the correct information. I used the following command and got the following results.
siege -b http://localhost/benchmark/posts/using_relations Transactions: 1100 hits Availability: 100.00 % Elapsed time: 63.21 secs Data transferred: 1.50 MB Response time: 0.55 secs Transaction rate: 17.40 trans/sec Throughput: 0.02 MB/sec Concurrency: 9.60 Successful transactions: 1100 Failed transactions: 0 Longest transaction: 1.76 Shortest transaction: 0.10
Using RequestAction with a string URL
Up next was using request action with a string url. String URL's are often the slower way to perform a requestAction as parsing the URL string is one of the more expensive operations in request dispatching. I used the following command and the best results were.
siege -b http://localhost/benchmark/posts/using_requestaction Transactions: 1100 hits Availability: 100.00 % Elapsed time: 64.60 secs Data transferred: 1.51 MB Response time: 0.57 secs Transaction rate: 17.03 trans/sec Throughput: 0.02 MB/sec Concurrency: 9.72 Successful transactions: 1100 Failed transactions: 0 Longest transaction: 1.76 Shortest transaction: 0.11
RequestAction with an Array URL
Up next is
requestAction() witn an array url. Using an array URL is supposed to expedite the dispatching process as it bypasses much of the parameter parsing done by
Router. This theory turned out to be true, as Array URL's clocked in marginally faster than their string counterparts.
siege -b http://localhost/benchmark/posts/using_requestaction_array Transactions: 1100 hits Availability: 100.00 % Elapsed time: 64.08 secs Data transferred: 1.53 MB Response time: 0.57 secs Transaction rate: 17.17 trans/sec Throughput: 0.02 MB/sec Concurrency: 9.78 Successful transactions: 1100 Failed transactions: 0 Longest transaction: 1.66 Shortest transaction: 0.11
RequestAction using Array URL's and Caching
In my mind this was going to be the most performant requestAction option, due to the cached nature. The results were as expected with this method clocking to be only slightly behind the relation call. It is important to note as well, that this test does not reflect the time savings earned from not having to make an additional query/ round of result parsing. In a real world situation, the savings of using a cached element would be magnified by the cost of the query.
siege -b http://localhost/benchmark/posts/using_cached_requestaction Transactions: 1100 hits Availability: 100.00 % Elapsed time: 63.60 secs Data transferred: 1.52 MB Response time: 0.56 secs Transaction rate: 17.30 trans/sec Throughput: 0.02 MB/sec Concurrency: 9.62 Successful transactions: 1100 Failed transactions: 0 Longest transaction: 1.77 Shortest transaction: 0.09
In case you quickly scanned through the full results here is a summary of what happened.
|Method||Requests per second (mean)||Total time taken (seconds)|
|Using requestAction and string urls||17.03||64.60|
|Using requestAction and array urls||17.17||64.08|
|Using cached requestaction||17.30||63.60|
requestAction() can be slower than a direct method call. There are some benefits to using requestAction though.
- You have the opportunity to reduce the number of repeated lines of code by putting the requestAction inside the element. In doing so, you create an encapsulated element, that can be included anywhere without having to worry about having the correct method calls in your controller.
- You can more easily cache the element. By using requestAction in conjunction with element caching you have an easy to use, simple to implement caching. Getting the same results with model method calls in your controller requires additional caching logic in your models.
- The potential for increased performance. As we saw in the benchmarks above, a cached element performed almost as fast as the direct method call. This margin will grow when a database query is added into the mix.
Now am I retracting my previous stance on requestAction? No, I still feel that there are many situations where requestAction is the incorrect solution and signals poor application design. However, when the need arises it is good to know that requestAction can be as fast or faster than other approaches when implemented properly.
You can download all the files I used for this process and run your own tests as well.