The Tests
Here’s the code I added to the PerformanceTests.cs class file. Use this code, in conjunction with the download further down to perform the tests yourself if you're familiar with LLBLGen. Here are the LLBLGen Adapter Tests://LLBLGen Adapter var adapter1 = new DataAccessAdapter(Program.connectionString, true, CatalogNameUsage.Clear, null); adapter1.OpenConnection(); tests.Add(id => adapter1.FetchEntity(new PostEntity(id)), "LLBLGen Adapter (FetchEntity w/ KeepConnectionOpen)"); var adapter2 = new DataAccessAdapter(Program.connectionString, false, CatalogNameUsage.Clear, null); adapter2.OpenConnection(); tests.Add(id => adapter2.FetchEntity(new PostEntity(id)), "LLBLGen Adapter (FetchEntity)"); var adapter3 = new DataAccessAdapter(Program.connectionString, true, CatalogNameUsage.Clear, null); adapter3.OpenConnection(); tests.Add(id => { var bucket = new RelationPredicateBucket(); bucket.PredicateExpression.Add(PostFields.Id == id); var entities = new EntityCollection(); adapter3.FetchEntityCollection(entities, bucket); entities.First(); }, "LLBLGen Adapter (Predicate w/ KeepConnectionOpen)"); var adapter4 = new DataAccessAdapter(Program.connectionString, false, CatalogNameUsage.Clear, null); adapter4.OpenConnection(); tests.Add(id => { var bucket = new RelationPredicateBucket(); bucket.PredicateExpression.Add(PostFields.Id == id); var entities = new EntityCollection(); adapter4.FetchEntityCollection(entities, bucket); entities.First(); }, "LLBLGen Adapter (Predicate)");Here are the LLBLGen Self-Servicing Tests:
//LLBLGen SelfServicing CommonDaoBase.ActualConnectionString = Program.connectionString; tests.Add(id => (new LLBLGenSS.EntityClasses.PostEntity()).FetchUsingPK(id), "LLBLGen SelfServicing (UC Fetch)"); tests.Add(id => new LLBLGenSS.EntityClasses.PostEntity(id, (IPrefetchPath)null), "LLBLGen SelfServicing (PK in constructor)"); tests.Add(id => { var entities = new LLBLGenSS.CollectionClasses.PostCollection(); entities.GetMulti(LLBLGenSS.HelperClasses.PostFields.Id == id); entities.First(); }, "LLBLGen SelfServicing (Predicate)"); var transactionManager = new LLBLGenSS.HelperClasses.Transaction(IsolationLevel.ReadUncommitted, "Test", Program.connectionString); tests.Add(id => { var entity = new LLBLGenSS.EntityClasses.PostEntity(); transactionManager.Add(entity); entity.FetchUsingPK(id); }, "LLBLGen SelfServicing (UC Fetch in Transaction)");Now let’s see the results.
The Results
In plain text format:Strategy | Time |
---|---|
Dynamic Mapper Query (buffered) | 57ms |
Mapper Query (non-buffered) | 58ms |
Mapper Query (buffered) | 59ms |
Dynamic Mapper Query (non-buffered) | 59ms |
hand coded | 59ms |
PetaPoco (Fast) | 63ms |
Dapper.Cotrib | 64ms |
OrmLite QueryById | 64ms |
PetaPoco (Normal) | 67ms |
Dynamic Massive ORM Query | 69ms |
BLToolkit | 95ms |
Simple.Data | 98ms |
Linq 2 SQL Compiled | 106ms |
LLBLGen Adapter (FetchEntity w/ KeepConnectionOpen) | 121ms |
SubSonic Coding Horror | 123ms |
NHibernate Session.Get | 124ms |
LLBLGen SelfServicing (UC Fetch in Transaction) | 126ms |
LLBLGen Adapter (Predicate w/ KeepConnectionOpen) | 127ms |
NHibernate SQL | 128ms |
Entity framework CompiledQuery | 135ms |
NHibernate HQL | 141ms |
LLBLGen Adapter (FetchEntity) | 145ms |
LLBLGen SelfServicing (UC Fetch) | 148ms |
LLBLGen SelfServicing (PK in constructor) | 150ms |
LLBLGen Adapter (Predicate) | 155ms |
LLBLGen SelfServicing (Predicate) | 164ms |
NHibernate Criteria | 175ms |
Soma | 186ms |
Linq 2 SQL ExecuteQuery | 240ms |
Linq 2 SQL | 737ms |
NHibernate LINQ | 769ms |
Entity framework ESQL | 812ms |
Entity framework ExecuteStoreQuery | 829ms |
Entity framework | 1063ms |
Entity framework No Tracking | 1065ms |
SubSonic ActiveRecord.SingleOrDefault | 4874ms |
Sam, I found the bit of code I wrote a while back. I refactored it a little, and updated the post.
I tried to download the insert sample and could not. Can you ensure your insert/delete etc batch is both in a transaction and using the batch interface eg: cnn.Execute(“insert t values {@a)”, new[] { new {a = 1} , new {a =2}]}, transaction: trans)
[…] Adding LLBLGen to the Dapper performance benchmark test project (Matt Cowan) […]
Nice 🙂
Though I still think benchmarks on data-access code is a bit of a gamble… for example: we profile our code a lot, making everything that is a tiny bit slow faster etc. however we also know that the things which makes our framework slower than e.g. dapper or the other micro-ORMs is that during fetching or other db interactions, some features have to be called, simply because they’re supported in the framework. This makes things less ideal from the raw performance point of view (so it might look ‘less ideal’) but from the ‘features vs. performance’ point of view it’s a different picture.
Nevertheless, code shouldn’t perform unnecessarily slow, so a user must be able to trust the developers who wrote the framework that they did everything they could do to make it perform as fast as possible. I’m glad the tests show we did.
There’s always room for improvements though. But in general those optimizations aren’t free: always something has to pay the price, e.g. a feature is not supported anymore, or code is less maintainable etc.
I agree, well said, and these tests are not meant to be gospel, but they are indicative. I also agree that features should be included in product comparisons. It would be monumental however to test every feature combination across each ORM :-), especially when you take designers, modelers, code-generation templating, apis and more into consideration. There’s a range of acceptability in these things, in my opinion, unless one is building a system where raw speed is the primary measure. It’s nice in the end that LLBLGen didn’t sacrifice performance for the sake of features.