1

I have had memoryleak issues in CodeFluent because eventhandlers of ListCollections of objects maintained references to objects I did not need anymore. The solution was to change the collection type of the entity to List in stead of ListCollection. That solved the memory leak problems.

However, now I noticed that the List is MUCH slower than the ListCollection. Every time Codefluent adds an object to the List if checks whether the object is already in the list. This will fire the BaseContains method in the object. 91% of CPU is spent here (profiling using ANTS). I have marked the Hot path with CPU percentage.

The function LoadByMainCwEntity contains the following code block:

for (readerRead = reader.Read(); ((readerRead == true) 
            && ((count < this.MaxCount) 
            && (count < pageSize))); readerRead = reader.Read())
{
    readCount = (readCount + 1);
    if ((CodeFluent.Runtime.CodeFluentPersistence.CanAddEntity(pageIndex, pageSize, pageOptions, readCount) == true))
    {
        Runtime.CwObject cwObject = new Runtime.CwObject();
        ((CodeFluent.Runtime.ICodeFluentEntity)(cwObject)).ReadRecord(reader);
        91% CPU >> if ((this.BaseContains(cwObject) == false))
        {
            this.BaseAdd(cwObject);
            count = (count + 1);
        }
        cwObject.EntityState = CodeFluent.Runtime.CodeFluentEntityState.Unchanged;
    }
}

Which calls this:

protected virtual bool BaseContains(Runtime.CwObject cwObject)
        {
            if ((cwObject == null))
            {
                return false;
            }
            91% CPU >> bool localContains = this.BaseList.Contains(cwObject);
            return localContains;
        }

Which calls this:

public virtual bool Equals(Runtime.CwObject cwObject)
    {
        if ((cwObject == null))
        {
            return false;
        }
        29% CPU >> if ((this.Guid.Equals(CodeFluentPersistence.DefaultGuidValue) == true))
        {
            return base.Equals(cwObject);
        }
        45% CPU >> return (this.Guid.Equals(cwObject.Guid) == true);
    }

All methods seem light. I think the problem lies in the hitcount. If I have a list of 100.000 objects and Codefluent adds number 100.001 it will check all 100.000 others to find a match. A growing collection will exponentially slow down the .Add method.

It seems a bit odd to check whether the object is already in the collection in a normal 'load' operation of Codefluent. Is there any workaround or should I just live with the fact large Lists are really slow in Codefluent?

Peter de Bruijn
  • 792
  • 6
  • 22
  • Beyond the fact that I would not manipulate collections/list/whatever of 100000+ objets in .NET (heck, that's what relational databases are strong at, manipulate sets of things), you can write an aspect that will remove the BaseContains calls, which is indeed, as you point it, the bottleneck, if you're sure it's not needed (what CodeFluent cannot be sure of) – Simon Mourier Jan 29 '17 at 10:10

1 Answers1

1

SoftFluent has created an aspect that improves the performance of the generated code by removing some checks and genericity: blog post, code on GitHub. In the benchmark provided by Frans Bouma, using this aspect, CodeFluent Entities is ranked at the 2nd place just after the hand coded query.

If you have identified just a little number of load methods that are slow in the context of your application, you can create custom C# methods that use the generated code to return a custom collection. For instance you can create a method which returns an IEnumerable<T> instead of the generated collection:

static IEnumerable<Order> LoadOrders()
{
    using (IDataReader reader = OrderCollection.PageDataLoadAll(null))
    {
        while (reader.Read())
        {
            Order o = new Order();
            o.RaisePropertyChangedEvents = false;
            ((ICodeFluentEntity)o).ReadRecord(reader);
            yield return o;
        }
        CodeFluentPersistence.CompleteCommand(Constants.NorthwindStoreName);
    }
}

Update

The fastReader attribute should be visible in the property grid when you select a property on the surface. You must select the "Aspect and Producers Properties" tab:

Aspects and producers Properties

The property is added by the aspect:

<cf:descriptor name="fastReader" targets="Property" defaultValue="false" displayName="Enable Fast Reader" typeName="boolean" description="Determines if the fast reder is enabled for collection loading." category="Faster Reader Aspect" />

The aspect remove automatic conversions (CodeFluentPersistence.GetReaderValue) and expect the stored procedures to return all the columns. In the some cases, it may not work.

meziantou
  • 20,589
  • 7
  • 64
  • 83
  • Hi Meziantou, I have tried your suggestion and indeed, some parts of the application are ~450 times faster now. Wow! I had to change the aspect to work because I could not figure out how to set the "fastReader" attribute value on the entity. I would have expected that a new property would show in de property editor but I don't see it. I have 2 additional questions: 1) How/where do I set the attribute value "fastReader"? 2) Can you think of one reason NOT to set the attribute on every entity? – Peter de Bruijn Jan 30 '17 at 08:22
  • By the way, there is an unhandled exeption in the aspect in the 'Resolve' method. The project won't build if the variable 'Property' is null because of the following line: Console.WriteLine("prop:" + Property.Name + " nullable:" + Property.IsNullable + " mn:" + Property.IsModelNullable); If you change it to if (Property == null) { Console.WriteLine("Property is null"); } else { Console.WriteLine("prop:" + Property.Name + " nullable:" + Property.IsNullable + " mn:" + Property.IsModelNullable); } it works. – Peter de Bruijn Jan 30 '17 at 09:42
  • I see the property now, but I am confused why the aspect is set on the property, not the entity itself? In fact, if I set the aspect on the property nothing happens. If i cut an paste it the code to the entity in the cfp it works as expected. When debugging the aspect I also see it looks at the aspect value of the entity and not the property. – Peter de Bruijn Jan 30 '17 at 10:07
  • Everything seems to work for the fastread enabled entity, only the _rowversion does not work. this._rowVersion = reader.GetByte(indices[37]); Throws the following error at compile time: Cannot implicitly convert 'byte' to 'byte[]'; – Peter de Bruijn Jan 30 '17 at 10:08
  • Feel free to edit the code of the aspect to match your needs. The `GetXXX` method name is computed at [line 299](https://github.com/SoftFluent/CodeFluent-Entities/blob/master/Extensions/SoftFluent.FasterReader/SoftFluent.FasterReader.xml#L299) and does not handle array type. You can add a condition like `typeof(byte[]).IsAssignableFrom(prop.ClrType)` => `GetBytes` – meziantou Jan 30 '17 at 10:56