IEnumerable из DBContext против IEnumerable из кода Использование памяти - PullRequest
1 голос
/ 17 октября 2019

Я запутался с проблемой использования памяти IEnumerable, особенно сравнивайте источник данных IEnumerable из БД и источник данных IEnumerable из кода yield return значения const.

У меня есть функция Memory для проверки памятииспользование.

        static string Memory()
        {
            return (Process.GetCurrentProcess().WorkingSet64 / (1024 * 
                    1024)).ToString();
        }
  1. Так что здесь я использую EF CORE 3.0, получая доступ к таблице 150000 записей
            using DataContext context = new DataContext();

            Console.WriteLine(Memory()); //21

            IEnumerable<User> users = context.Users;
            foreach (var i in users) {}

            Console.WriteLine(Memory());//101
            Console.WriteLine(GC.GetTotalMemory(true));//46620032

, по некоторым причинам я не могу загрузить фото, поэтому мне нужно напечатать результаты извините за это. (результаты в коде в виде комментариев).

И в следующем примере используется yield return для генерации данных IEnumerable.
        static IEnumerable<User> Generator(int max)
        {
            for (int i = 0; i < max; i++)
            {
                yield return new User { Id = 1, Name = "test" };
            }
        }

вот результат

            Console.WriteLine(Memory());// 21

            IEnumerable<User> users = Generator(150000);
            foreach (var i in users){}

            Console.WriteLine(Memory());// 24
            Console.WriteLine(GC.GetTotalMemory(true)); // 658040

Теперь я очень запуталсяна примере 1 и 2. Насколько я понимаю, для источника данных IEnumerable он будет читать по одному, а не всю коллекцию, поэтому он может уменьшить использование памяти, как в примере 2. Однако, когда речь идет об использованииEF CORE (я знаю, что это не относится к EF CORE, но мне нужен конкретный пример для этого.), Я думаю, что он все еще тянет один за другим, но мой вопрос - почему он использует гораздо больше памяти, чем второй пример. Так это тянет каждую запись одну за другой? И в конце у меня все записи из БД в памяти это правильно? Но почему второй использовать так меньше памяти? Я даю те же записи. Если кто-то может объяснить это очень ценится. Спасибо !!!

Ответы [ 3 ]

2 голосов
/ 17 октября 2019

Это действительно специфичное для EF (Core) поведение, называемое отслеживанием (изменением), объясненное в Отслеживание против запросов без отслеживания . Обратите внимание, что отслеживание является поведением по умолчанию, если вы не измените его явно

context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

или используете AsNoTracking() в источнике запроса.

Важно то, что, хотя результат запросаоцениваемый один за другим, экземпляр DbContext добавляет каждый созданный экземпляр сущности, а также некоторую дополнительную информацию, такую ​​как состояние и моментальный снимок исходных значений, в некоторый внутренний список. Таким образом, даже без снимка ключа, состояния и исходных значений эквивалентный код для генератора будет выглядеть примерно так:

IEnumerable<User> users = Generator(150000);
var trackedUsers = new List<User>();
foreach (var i in users)
{
    trackedUsers.Add(i);
}

Таким образом, в конце цикла все созданные экземпляры во время итерации будут храниться в памяти. .

Вот почему вы можете рассмотреть возможность использования опции AsNoTracking на тот случай, если вам понадобится выполнить запрос сущности и повторить его один раз. Обратите внимание, что запросы, не относящиеся к объектам (проекциям) и объектам без ключей, не отслеживают их результаты, так что это действительно поведение, специфичное для запросов объектов.

0 голосов
/ 17 октября 2019

Насколько я понимаю, для источника данных IEnumerable он будет читать по одному, а не всю коллекцию, поэтому он может уменьшить использование памяти, как в примере 2.

Это не относится к Linq-to-Entities. Он запустит запрос для получения всех данных и просто позволит вам выполнить итерацию по ним после загрузки.

В может быть возможны некоторые оптимизации в отношении подкачки и т. Д., Которые могут происходить у некоторых провайдеров, но, как правило, EF не извлекает записи «одна за другой» из базы данных. Данные будут храниться в контексте, который добавляет накладные расходы памяти. Если вы покончили с контекстом после того, как с ним покончено (что является наилучшей практикой), вы можете увидеть резкое уменьшение памяти.

Но почему второй использует меньше памяти?

Поскольку в цикле вы создаете объект, возвращаете его, а затем ничего не делаете с ним. Таким образом, каждый объект подходит для сбора мусора очень быстро, и, следовательно, общий объем используемой памяти будет меньше. Кроме того, у вас нет накладных расходов на DbContext (который не должен быть огромным)

Обратите внимание, что сборка мусора не детерминирована. Вполне возможно, что при правильных обстоятельствах ничего не будет собрано, и вы увидите, что второй цикл использует гораздо больше памяти.

0 голосов
/ 17 октября 2019

В вашем коде после выполнения оператора foreach EF переходит в базу данных и извлекает все записей в память и перечисляет результаты. Это то же самое, что и:

var list = context.Users.ToList();
foreach (user u in list)
{
}

Не зная, как определяется класс User, трудно сказать, почему потребление памяти такое, как оно есть (и я не говорю, что оно слишком высокое), нокак только вы получаете сущности в EF, за кулисами происходит много событий, таких как отслеживание изменений, которые потребляют память.

и, кстати, IQueryable - это IEnumerable

public interface IQueryable<out T> : IEnumerable<T>, IEnumerable, IQueryable
...