1
votes

IEnumerable à partir de DBContext vs IEnumerable à partir du code Utilisation de la mémoire

Je suis confus avec le problème d'utilisation de la mémoire IEnumerable, en particulier comparez la source de données IEnumerable de la base de données et la source de données IEnumerable à partir des valeurs de code yield return const.

J'ai une mémoire pour vérifier l'utilisation de la mémoire.

            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. J'utilise donc EF CORE 3.0 et j'accède à un total de 1 50000 tables d'enregistrements
        static IEnumerable<User> Generator(int max)
        {
            for (int i = 0; i < max; i++)
            {
                yield return new User { Id = 1, Name = "test" };
            }
        }

pour une raison quelconque, je ne peux pas télécharger de photos, donc je dois taper les résultats désolé à ce sujet. (les résultats sont dans le code sous forme de commentaires).

  1. Et l'exemple suivant utilise yield return pour générer les données IEnumerable.
            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

voici le résultat

        static string Memory()
        {
            return (Process.GetCurrentProcess().WorkingSet64 / (1024 * 
                    1024)).ToString();
        }

Maintenant, je suis très confus par les exemples 1 et 2. Ma compréhension est que pour la source de données IEnumerable, il en lira une à la fois, plutôt que toute la collection, ce qui permettra de réduire l'utilisation de la mémoire, tout comme l'exemple 2. Cependant, lorsqu'il s'agit d'utiliser EF CORE (je sais que cela n'est pas spécifique à EF CORE, mais j'ai besoin d'un exemple concret pour cela.), Je pense qu'il tire toujours un par un, mais ma question est de savoir pourquoi il utilise tellement plus de mémoire que le deuxième exemple. Alors est-ce que ça tire chaque disque un par un? Et à la fin, j'ai tous les enregistrements de DB en mémoire est-ce correct? Mais pourquoi la seconde utilise donc moins de mémoire? Je cède les mêmes records. Si certains pouvaient expliquer cela est très apprécié. Merci !!!


3 commentaires

EF Core ne tire pas les enregistrements un par un et renvoie en fait IQueryable et non IEnumerable


@VidmantasBlazevicius lorsque j'itère la collection, elle doit être IEnumerable ne peut pas être IQueryable . Et il tire un par un car les deux iqueryable et ienumerable sont une exécution différée.


IQueryable étend IEnumerable , donc tout ce que vous faites sur un IEnumerable vous pouvez également le faire sur un IQueryable < / code>.


3 Réponses :


-1
votes

Dans votre code, une fois que vous exécutez l'instruction foreach, EF va dans la base de données et récupère tous les enregistrements en mémoire et énumère les résultats. C'est la même chose que:

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

Sans savoir comment la classe User est définie, il est difficile de dire pourquoi la consommation de mémoire est telle qu'elle est (et je ne dis pas qu'elle est trop élevée) , mais une fois que vous obtenez des entités dans EF, il se passe beaucoup de choses en coulisses, comme le suivi des modifications, qui consomment de la mémoire.

et BTW, IQueryable est IEnumerable

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


0 commentaires

-1
votes

Je crois comprendre que pour la source de données IEnumerable, elle va en lire une à la fois, plutôt que toute la collection, ce qui peut réduire l'utilisation de la mémoire, comme dans l'exemple 2.

Ce n'est pas vrai pour Linq-to-Entities. Il exécutera une requête pour obtenir toutes les données, et vous permettra simplement de les parcourir une fois qu'elles sont chargées.

Il peut y avoir des optimisations en ce qui concerne la pagination, etc. qui pourraient arriver par certains fournisseurs, mais en général EF ne tire pas les enregistrements "un par un" de la base de données. Les données seront stockées dans le contexte, ce qui ajoute une surcharge de mémoire. Si vous supprimez le contexte une fois que vous en avez terminé (ce qui est une bonne pratique), vous constaterez peut-être une diminution spectaculaire de la mémoire.

Mais pourquoi la seconde utilise donc moins de mémoire?

Parce que dans la boucle, vous créez un objet, vous le retournez, puis ne faites rien d'autre avec lui. Ainsi, chaque objet est éligible pour le garbage collection très rapidement, et donc la mémoire totale utilisée sera inférieure. De plus, vous n'avez pas la surcharge du DbContext (qui ne devrait pas être énorme)

Notez que le garbage collection n'est pas déterministe. Il est possible que, dans les bonnes circonstances, rien ne soit collecté et que vous voyiez beaucoup plus de mémoire utilisée par la deuxième boucle.


0 commentaires

2
votes

Il s'agit en effet d'un comportement spécifique à EF (Core) appelé suivi (des modifications), expliqué dans Requêtes avec suivi ou sans suivi . Notez que le suivi est le comportement par défaut si vous ne le modifiez pas explicitement

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

ou n'utilisez pas AsNoTracking () sur la source de la requête.

L'essentiel est que même si le résultat de la requête est évalué un par un, l'instance DbContext ajoute chaque instance d'entité créée ainsi que des informations supplémentaires telles que l'état et l'instantané des valeurs d'origine dans une liste interne. Donc, même sans instantané de clé, de statut et de valeurs d'origine, le code équivalent pour le générateur serait quelque chose comme ceci:

context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

Donc, à la fin de la boucle, vous auriez tous créé instances pendant l'itération stockées en mémoire.

C'est pourquoi vous pouvez envisager d'utiliser l'option AsNoTracking au cas où vous en auriez besoin pour exécuter une requête d'entité et l'itérer une fois. Notez que les requêtes non d'entité (projection) et les entités sans clé ne suivent pas leurs résultats, il s'agit donc vraiment d'un comportement spécifique aux requêtes d'entité.


12 commentaires

Je pense que c'est la bonne réponse à ma question, je vais bientôt essayer pour le prouver.


J'ai ajouté AsNoTracking et la mémoire diminue considérablement, mais toujours pas la même chose que yield return , peut-être est-ce à cause de quelque chose d'ef core interne? (Il fait toujours quelque chose qui ne peut pas être optimisé)


et aussi je veux juste vérifier que, ef tire les disques un par un, non? plutôt que de charger toute la collection en une seule fois. même je fais var users = context.Users.ToList (); il y aura une requête SQL et en tirant un par un, puis enregistrer les enregistrements en mémoire, est-ce correct?


Le résultat de l'exécution de la requête est le lecteur de données ADO.NET, qui est ensuite itéré un par un et matérialisé en objets. Voir Fonctionnement de la requête . var users = context.Users.ToList (); est juste un raccourci pour var users = new List (); foreach (var user in context.Users) users.Add (user); , donc oui, c'est une requête et ensuite créer et enregistrer les objets en mémoire.


oh, donc même s'il s'agit d'une requête sans suivi, ef a encore des vérifications et suit les résultats, c'est peut-être la raison pour laquelle l'utilisation de la mémoire est encore un peu plus élevée que yield return ? Qu'est-ce que tu en penses?


Le dernier dépend de la version EF Core. - en 3.0 le suivi local des requêtes sans suivi a été supprimé en raison de " performances supplémentaires et surcharge de mémoire " qu'il ajoutait . Donc, si vous testez avec 1.x / 2.x, vous voyez probablement cette surcharge de mémoire.


ah, je vois, cela a beaucoup de sens pour moi. Désolé, une dernière chose que je veux demander, c'est pour autant que je sache, lorsque je demande des données à EF, cela me donne toujours la source de données IEnumerable. Je ne peux pas obtenir toute la collection en une seule fois, ce qui peut causer de graves problèmes lors de la consommation de Big Data. droit? Comme DbSet implémente IEnumerable, il me donne toujours les enregistrements un par un. Il n'y a pas de moyen facile d'obtenir toute la collection dans son ensemble.


j'espère que je comprends bien, merci beaucoup pour votre temps et vos connaissances.


Vous pouvez facilement obtenir toute la collection en exécutant la requête représentée par l'ensemble de bases de données sans aucun filtre et mettre en mémoire tampon les résultats dans let say list ( ToList () ). Dans les coulisses, il obtiendra toujours les données par blocs du serveur de base de données, car c'est ainsi que fonctionnent les lecteurs de données (curseurs de base de données). Les données doivent être transférées d'une manière ou d'une autre de la base de données au client et converties en objets par la couche ORM, puis elles peuvent facilement être mises en cache (stockées dans une collection en mémoire) ou simplement itérées et traitées un par un ...


… Les deux scénarios sont facilement supportés par IEnumerable et ToList / ToArray et des méthodes d'assistance similaires. Quoi qu'il en soit, content d'être utile, bravo.


merci encore pour votre aide, vous êtes incroyable. cela vous dérange-t-il si je peux suivre vos réseaux sociaux comme Twitter ou quelque chose de similaire?


He-he, j'ai bien peur que SO soit le seul genre d'endroit "social" dans lequel je suis. Mais merci de m'avoir demandé, et vous êtes les bienvenus :)