Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity data querying and memory leak

I am downloading a lot of data in a loop but after some operations I remove them but what I see is that memory allocation is growing really fast, few seconds and 1GB, so how can clean after each iteration?

    using (var contex = new DB)
    {

        var inputs = contex.AIMRInputs.Where(x => x.Input_Type == 1);

        foreach (var input in inputs)
        {
            var data = contex.Values.Where(x => x.InputID == input.InputID).OrderBy(x => x.TimeStamp).ToList();

            if (data.Count == 0) continue;
            foreach (var value in data)
            {
               Console.WriteLine(Value.property);
            }
            data.Clear();


        }
    }
like image 794
kosnkov Avatar asked Dec 09 '22 01:12

kosnkov


1 Answers

The first thing you can do, is disabling change tracking because you are not changing any data in your code. This prevents that the loaded objects get attached to the context:

For DbContext (EF >= 4.1):

var inputs = contex.AIMRInputs.AsNoTracking()
    .Where(x => x.Input_Type == 1);

And:

var data = contex.Values.AsNoTracking()
    .Where(x => x.InputID == input.InputID)
    .OrderBy(x => x.TimeStamp)
    .ToList();

Edit

For EF 4.0 you can leave your queries as they are but add the following as the first two lines in the using block:

contex.AIMRInputs.MergeOption = MergeOption.NoTracking;
contex.Values.MergeOption = MergeOption.NoTracking;

This disables change tracking for ObjectContext.

Edit 2

...especially refering to @James Reategui's comment below that AsNoTracking reduces memory footprint:

This is often true (like in the model/query of this question) but not always! Actually using AsNoTracking can be counterproductive regarding memory usage.

What does AsNoTracking do when objects get materialized in memory?

  • First: It doesn't attach the entity to the context and therefore doesn't create entries in the context's state manager. Those entries consume memory. When using POCOs the entries contain a snapshot of the entity's property values when it was first loaded/attached to the context - basically a copy of all (scalar) properties in addition to the object itself. So the comsumed memory takes (roughly) twice as much as the object's size when AsNoTracking is not applied.

  • Second: On the other hand, when entities don't get attached to the context EF cannot leverage the advantage of identity mapping between key values and object reference identities. This means that objects with the same key will be materialized multiple times which comsumes additional memory while without using AsNoTracking EF will ensure that an entity is only materialized once per key value.

The second point becomes especially important when related entities are loaded. Simple example:

Say, we have an Order and a Customer entity and an order has one customer Order.Customer. Say the Order object has the size 10 byte and the Customer object the size 20 byte. Now we run this query:

var orderList = context.Orders
    .Include(o => o.Customer).Take(3).ToList();

And suppose all 3 loaded orders have the same customer assigned. Because we didn't disable tracking EF will materialize:

  • 3 orders objects = 3x10 = 30 byte
  • 1 customer object = 1x20 = 20 byte (because the context recognizes that the customer is the same for all 3 orders it materializes only one customer object)
  • 3 order snapshot entries with original values = 3x10 = 30 byte
  • 1 customer snapshot entry with original values = 1x20 = 20 byte

Sum: 100 byte

(For simplicity I assume that the context entries with the copied property values have the same size as the entities themselves.)

Now we run the query with disabled change tracking:

var orderList = context.Orders.AsNoTracking()
    .Include(o => o.Customer).Take(3).ToList();

The materialized data are:

  • 3 orders objects = 3x10 = 30 byte
  • 3 (!) customer objects = 3x20 = 60 byte (No identity mapping = multiple objects per key, all three customer objects will have the same property values, but they are still three objects in memory)
  • No snapshot entries

Sum: 90 byte

So, using AsNoTracking the query consumed 10 byte less memory in this case.

Now, the same calculation with 5 orders (Take(5)), again all orders have the same customer:

Without AsNoTracking:

  • 5 orders objects = 5x10 = 50 byte
  • 1 customer object = 1x20 = 20 byte
  • 5 order snapshot entries with original values = 5x10 = 50 byte
  • 1 customer snapshot entry with original values = 1x20 = 20 byte

Sum: 140 byte

With AsNoTracking:

  • 5 orders objects = 5x10 = 50 byte
  • 5 (!) customer objects = 5x20 = 100 byte
  • No snapshot entries

Sum: 150 byte

This time using AsNoTracking was 10 bytes more expensive.

The numbers above are very rough, but somewhere is a break-even point where using AsNoTracking can need more memory.

The difference in memory consumption between using AsNoTracking or not strongly depends on the query, the relationships in the model and the concrete data that are loaded by the query. For example: AsNoTracking would be always better in memory consumption when the orders in the example above all (or mostly) have different customers.

Conclusion: AsNoTracking is primarily meant as a tool to improve query performance, not memory usage. In many cases it will also consume less memory. But don't be surprised if a specific query needs more memory with AsNoTracking. In the end you must measure the memory footprint for a solid decision in favor or against AsNoTracking.

like image 95
Slauma Avatar answered Dec 11 '22 15:12

Slauma