3

Anyone know anything about why linq queries are about 6 times slower when querying using active record vs simplerepository? The below code runs 6 times slower than when i query the data using a simple repository. This code is executed 1000 times in a loop

Thanks in advance

        string ret = "";            
//      if (plan == null)
        {
           plan =VOUCHER_PLAN.SingleOrDefault(x => x.TENDER_TYPE == tenderType);
        }
        if (plan == null)
           throw new InvalidOperationException("voucher type does not exist." + tenderType);

        seq = plan.VOUCHER_SEQUENCES.First();
        int i = seq.CURRENT_NUMBER;
        seq.CURRENT_NUMBER += seq.STEP;
        seq.Save();
Adam Cooper
  • 8,077
  • 2
  • 33
  • 51
aboutme
  • 235
  • 2
  • 8
  • Have you compared the SQL that is executed for ActiveRecord/SimpleRepository – Adam Cooper Jan 17 '10 at 15:46
  • Have you done what Adam suggested. I would be keen to know if you find an answer. – Jon Jan 21 '10 at 11:34
  • Funny enough ive profiled the SQL and no call takes longer than a few ms. It seems to be bottlenecked somewhere in the application :/ – aboutme Jan 29 '10 at 01:46
  • Do you notice your VOUCHER_PLAN constructor is getting called a bunch of times? You might have some sort of ToList() going on in there instantiating all your values but I don't really know how to avoid it beyond trying SimpleRepository. – nvuono Apr 16 '10 at 04:02
  • I have the same issue and it is driving me crazy. Getting a list of 350 records and the query/otw time is not even measurable. It still takes over 2 seconds to create the objects though, for an unknown reason. – Andrew May 27 '10 at 19:48
  • In a test app: *Linq2Sql: ~.11 seconds, SubSonic, ~1.1seconds.* In all cases after, subsonic is still 10x slower than linq2sql on this dataset. The 2 seconds was a misread on my part, it is what all the other too-slow queries were taking, this is the specific one at 1second. – Andrew May 27 '10 at 20:00

3 Answers3

2

We did some profiling on this and found SubSonic's record.SingleOrDefault(x=>x.id=someval) to be up to 20x slower than the same query done through CodingHorror. Logged it here: https://github.com/subsonic/SubSonic-3.0/issues/258.

The profiler pointed at this in ExecutionBuilder.cs:

// this sucks, but since we don't track true SQL types through the query, and ADO throws exception if you
// call the wrong accessor, the best we can do is call GetValue and Convert.ChangeType
Expression value = Expression.Convert(
    Expression.Call(typeof (Convert), "ChangeType", null,
                    Expression.Call(reader, "GetValue", null, Expression.Constant(iOrdinal)),
                    Expression.Constant(TypeHelper.GetNonNullableType(column.Type), typeof(Type))
        ),
    column.Type
    );

Disappointing because I really like SubSonic/Linq.

In the end we gave up and I wrote this - http://www.toptensoftware.com/petapoco. After porting, our load test showed requests per second went up and CPU load dropped from about 80% to 5%.

Brad Robinson
  • 44,114
  • 19
  • 59
  • 88
  • look also on this tests I have made http://stackoverflow.com/questions/2363735/benchmark-linq2sql-subsonic2-subsonic3-any-other-ideas-to-make-them-faster – Aristos Jun 27 '11 at 23:58
0

I was able to make a HUGE difference in performance by caching the database instance it creates in the constructor/init procedures. What I am seeing now is ~2-3x speed up, depending on the situation and the run.

1) The method of just replacing _db with a static instance works fine if you only call the default constructor, and has all the same speed benefits.

// MyProject.MyDB _db;
// replace with a static instance, and remove the "this." from other lines
static MyProject.MyDB _db = new MyDB();

public MyClass() {
    //_db=new MyProject.MyDB();
    Init();
}

2) I have written a little caching class for the DB entries and am calling that from my ActiveRecord.tt file in all the old places where "new()" was used.

// REPLACE "MyDB" with the name of your DB.  Alternately, include this 
// class in Context.tt and have it generate the correct name.  

class ContextDatabaseCache {        

    public static MyDB GetMyDB()
    {
        return GetInstance("~~default~~", () => new MyDB());
    }

    public static MyDB GetMyDB(string connectionString) {
        return GetInstance(connectionString, () => new MyDB(connectionString));
    }

    public static MyDB GetMyDB(string connectionString, string providerName)
    {
        return GetInstance(connectionString + providerName, () => new MyDB(connectionString, providerName));
    }

    private static Dictionary<string, MyDB> _dict = new Dictionary<string, MyDB>();
    private static MyDB GetInstance(string key, Func<MyDB> createInstance)
    {
        if (!_dict.ContainsKey(key)) {               
            lock (_dict) {
                if (!_dict.ContainsKey(key)) {
                    _dict.Add(key, createInstance());
                }
            }
        }
        return _dict[key];
    }

    ///<summary>Call this when the "DefaultConnection" string changes in the
    ///         App.exe.config file so that a new db instance will be created
    ///         and pick up the changed value. </summary>
    public static void Clear() {
         _dict.Clear();
    }

}

This is the type of replacement that was made in the ActiveRecord.tt file:

public <#=tbl.ClassName#>(){
    _db=new <#=Namespace#>.<#=DatabaseName#>DB();
    Init();            
}

    // becomes this: 
public <#=tbl.ClassName#>(){
    _db= <#=Namespace#>.ContextDatabaseCache.Get<#=DatabaseName#>DB();
    Init();            
}
Andrew
  • 8,322
  • 2
  • 47
  • 70
0

Apparently this "isn't an issue" with subsonic, though they know it is there. It will NOT be fixed. You have to use a crappy batch query syntax to get this, which no one will.

The thing I don't understand about that is that this is the 90% case. Get a list of records from a table. It should BE the fast one, not the slow one. Everyone does it, everywhere, all the time.

So many problems with subsonic. I had to write caching for the DB field => object field lookups, since they were so damn slow too.

Andrew
  • 8,322
  • 2
  • 47
  • 70
  • Hi Andrew, You are right. I was very much in love with subsonic when using 1.0.3. Speed up development and speed in execution. But when we ported it to 3.0, I am in lot of trouble related to speed. We are now trying 2.1 as we could not change subsonic entirely. Linq queries takes double the execution time then before. Thanks for your post for db caching. Could you please provide us the code for DB field => Object field lookups. It would be of great help. (email address removed) Thanks – Manish Pansiniya May 11 '11 at 13:52
  • Sorry, i went back through all the code I have, and I don't see the .TT changes for subsonic. It was a previous job, so I might not have taken them with me. I think it just followed the normal "find where it does it, write a simple hashmap based cache, and be done with it" pattern, like here. – Andrew Sep 19 '11 at 05:01