The upcoming Propel release, dubbed Propel 1.6.3, is faster than ever. Special optimizations were introduced in the Propel generator to make your entities blazingly fast when performing persistence actions.
ORMs have a bad reputation concerning performance. But complex models are often slow because the underlying database queries are slow. Before blaming the ORM, you must measure the share of the database in the processing time of a script. A slow SQL query can't be faster using an ORM.
That's why we, the Propel developers, measure the Propel performance relative to reference queries. Propel is not slow or fast per se. A Propel query is n times slower than the same query without an ORM, and we work hard to keep the n factor low.
In fact, our target is to keep the Propel ORM under a factor 4. That means that using Propel should not be more than four times slower than bare PDO.
In addition, Propel provides tools to minimize database queries, as explained in a previous post. Instance pooling, joined hydration, lazy loading, lazy hydration, all those features avoid a return trip to the database in many cases - and they can make Propel faster than raw PDO.
Four times slower than PDO can seem like a lot slower. It is not. Think about all the things that Propel has to do when you call
save() on an entity:
Authoralso saves all related
PDO::PARAM_BOOLfor a boolean column,
PDO::PARAM_NULLfor a null value, etc.)
That's a lot of object manipulations, and the actual
INSERT SQL query is only one step in this long recipe.
In previous Propel versions, a lot of the operations described above were executed at runtime. Crafting the SQL query string, choosing the PDO type for a modified column, getting the primary key value either before the main query (for tables using sequences) or after (for tables using autoincrement), etc. There were really many things to do each time an object was saved.
In the next Propel version, all the computation necessary for these operations is now done at build time. The generated
save() method actually does exactly what is described above, in the fastest PHP code possible. If you wrote a generic PHP script to do all the steps just described, it would probably be slower than Propel. Because the Propel generated classes aren't generic: they are specific to your model, and take away all the heavy duty stuff to introspect your schema, and map the object oriented world with the relational world.
Upgrade to the Propel master, rebuild your model, and take a look at the generated code for the
save() method. You will understand all that it does, even without knowing how Propel works. It's because the
save() method contains nothing Propel-specific. It's pure PHP and PDO. It's pure speed.
In Propel 1.6.2, the "n" factor of the insert operation was around 10. It used to take ten times longer to persist an entity using Propel than executing the insert statement with PDO alone. The "n" factor is now well under nine. It's also well under the target four.
We use a test project called php-orm-benchmark, released under the MIT license, to compare the speeds of some basic scenarios. In addition to a simple insertion, we measure the n factor for a database retrieval using a primary key, the execution of a complex query, the raw hydration of an object based on a resultset, and a joined hydration. Here are the results:
The tests repeats the basic scenarios enough times to reach an approximate score of 100 for PDO. That's the reference. So a score of 300 means that, compared to PDO, the library has an overhead of factor 3 for this scenario. According to the load of the server used for testing, results may vary of about 10%. So any difference of less than 10% is not significant.
What this chart shows is that simple operations used to be quite heavy in Propel. An insertion, or a Pk find, are very fast database operations. The Propel overhead for these operations was important - even if that didn't mean Propel was slow. If the raw SQL score (using PDO) was of 10ms for an insertion, the same operation would take 100ms with Propel. That's not slow per se, but it's slower than PDO.
The chart also shows that the Propel overhead becomes much less noticeable when the SQL query is complex. That's because the Propel overhead depends on the number of objects to hydrate, while the SQL query time depends on the complexity of the query (and the pertinence of the indices).
Finally, the chart shows that the latest version of Propel 1.6 reaches the sweet spot of the "4 factor", and even goes beyond.
You will ask this question, so we'd better give you the answer. Here is how Doctrine compares with Propel on these tests:
|Doctrine 1.2 with cache||2508||1500||665||1481||933|
As already noted in a previous benchmark, Doctrine 1.2 is VERY slow. The overhead varies between 6 and 25, which is a lot. As for Doctrine 2, it performs a lot better. In fact, it even outperforms Propel in the insertion scenario - because Doctrine 2 has a special feature to accelerate mass insertion. So a scenario made to test raw insertion speed, and repeated to make the duration significant, becomes a mass insertion, and therefore takes advantage of the Doctrine optimization.
Overall, Doctrine 2 performs very well, and keeps the ORM overhead under a factor 8 all the time.
We could boost the Propel results even further by using a cache engine, just like Doctrine does. But having to use cache brings a lot of worries: you have to think about naming the cached queries, and invalidating the cache when the underlying data changes. Unfortunately, these two are the hardest things in Computer Science, according to Phil Karton. So if you can avoid using a cache engine, by all means, do it.
Propel's raw speed is enough to remove the need of a cache engine. That's because Propel uses code generation to prepare the base entity and query classes for runtime. The philosophy of code generation was only pushed a little further in Propel 1.6, so you can now use the Propel ORM without any afterthought about performance.
Found a typo ? Something is wrong in this documentation ? Just fork and edit it !