IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Dotnet Discussion :

Optimisation (LINQ ?)


Sujet :

Dotnet

  1. #1
    Membre éprouvé Avatar de dacid
    Homme Profil pro
    Inscrit en
    Juin 2003
    Messages
    1 065
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Juin 2003
    Messages : 1 065
    Par défaut Optimisation (LINQ ?)
    Bonjour @ tous,

    Je fait une requête sur une grosse table SQL SERVER (320 000 enregistrements) avec pas mal de relations.
    Certes, la requête prend un peu de temps à exécuter, mais ce qui me chagrine, c'est que le le temps de chargement de mes objets est plus longue encore...

    J'aimerais savoir si vous voyez une faille dans ma façon de faire.
    En gros, j'ai une collection par type d'objet et pour chaque dépendances, je cherche dans la collection ad hoc s'il existe déjà. Si non, il l'ajoute.

    Exemple pour charger des articles :
    J'ai un objet tblArticle et un objet tblArticles qui contient principalement une collection (BindingList) de tblArticle.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
     
    Public Class tblArticles
        <DataMember>
        Public items As BindingList(Of tblArticle)
     
        Public Sub GetAll()
            Dim sb As StringBuilder = New StringBuilder($"Debut({DateTime.Now:mm:ss}) ")
            Dim dr As IDataReader
            Dim Fabs As tblFabricants = New tblFabricants(_Env)
     
            dr = _Env.OpenDr("SELECT * FROM ...")
            items.Clear()
    	sb.Append($"SqlExec({DateTime.Now:mm:ss}) ")
            While dr.Read
                items.Add(New tblArticle(_Env) With {
                    .Id = dr("ID").ToString().Trim(),
                    .Desc = dr("DES").ToString().Trim,
                    .Fab = Fabs.GetById(New tblFabricant(_Env) With {.Id = dr("FABID").ToString()})
                    ...etc...
                    })
            End While
            dr.Close() : dr.Dispose() : dr = Nothing
    	sb.Append($"Fin({DateTime.Now:mm:ss}, {items.Count} elmts).")
    	msgErr = sb.ToString()
        End Sub
    End Class
     
    Dans ma classe tblFabricants :
        ''' <summary>Retourne l'élément correspondant de la collection. S'il ne le trouve pas, il l'ajoute à la collection.</summary>
        Public Function GetById(o As tblFabricant) As tblFabricant
            If (String.IsNullOrEmpty(o.Id)) Then Return Nothing
            Dim elmt As tblFabricant= items.Where(Function(s) s.Equals(o)).SingleOrDefault()
            If (elmt Is Nothing) Then
                    elmt = o
                    items.Add(elmt)
                End If
            End If
            Return elmt
        End Function
     
    Dans ma classe tblFabricant:
        Public Overrides Function Equals(obj As Object) As Boolean
            Return ((Not obj Is Nothing) AndAlso (TypeOf (obj) Is tblFabricant) AndAlso (CType(obj, tblFabricant).Id = Id))
        End Function
    J'ai simplifié le code, il y a plus de GetById que ça (7 en tout), mais le principe est là.
    Pour info, le log: Debut(15:49) SqlExec(15:53) Fin(16:18, 362113 elmts).

    Est ce que faire du LINQ sur des listes annexes dans chaque boucle est optimisé (sinon, comment faire ?) ?
    Est ce que le fait d'utiliser une BindingList pour faire une collection est optimisé (je l'ai fait pour éviter des casts) ?

    [EDIT]
    J'ai ôté tous mes GetById pour faire directement du New... et en gros, ça ne change pas grand chose: Debut(20:33) SqlExec(20:37) Fin(20:55, 362113 elmts).
    18 secondes au lieu de 25, c'est toujours ça de prit, mais 18 c'est toujours énorme et ça me casse pas mal d'automatismes...
    [/EDIT]

    Merci d'avance.

  2. #2
    Expert confirmé

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 761
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 761
    Billets dans le blog
    21
    Par défaut
    Bonjour,

    Je pense que le soucis vient de ton IDataReader, ou plutôt de l'image que tu t'en fais. IDataReader est fait pour traiter de large volume de données, et ne mets donc pas toutes les données issues d'une requête en cache. Mais elle recupère ce dont elle a besoin au fur et à mesure.

    Dans la mesure où tu fais un traitement sur des données et que tu mets le résultat de ce traitement dans une collection, tu auras au final toutes les données au format traité. Aussi, dans ce cas, je ne passerai pas par un reader mais ferais directement une requête Linq (donc traduire ta requête SQL en linq) et faire un .ToList() à la fin de cette requête pour récupérer, en une seule fois, toutes les données issues de la requêtes. Ensuite, tu itères afin de traiter tes données brutes.

    De même, quand tu fais ta requête initiale, peut être qu'en incluant des jointures tu peux récupérer les éléments que tu récupères en faisant des appels à tes GetByID. Ainsi, potentiellement, tu pourrais récupérer toutes les données utiles en une seule requête , et ensuite gérer la mise en cache.

    Enfin, oui, utiliser une BindingList induit des pertes de performance. Une BindingList génère des événements dès lors qu'un objet est ajouté/supprimé/modifié. Si tu n'en a pas besoin, il vaut mieux partir sur une List (en précisant éventuellement une capacité à sa création, histoire d'éviter des allocations supplémentaires en cours de route). Et si tu en as besoin, mais que tu n'as pas besoin de ces événements lors du traitement de ta requête, tu peux passer d'abord par une List et ensuite générer une BindingList à partir de cette liste. Un petit test de perfomance à réaliser pour voir s'il y a un gain notable ou pas

  3. #3
    Membre éprouvé Avatar de dacid
    Homme Profil pro
    Inscrit en
    Juin 2003
    Messages
    1 065
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Juin 2003
    Messages : 1 065
    Par défaut
    Bonjour Dorinf,

    Merci pour ta rapidité.
    En gros, tu me dis de faire du LinqToSql ?
    J'ai entendu parler de ce concept mais ne l'ai jamais utilisé car il est très lié à entity, mais je ne l'utilise pas.
    Quels objet utiliser alors pour faire la connexion ?
    Aurais tu un bref exemple, STP ?

    Merci.

  4. #4
    Expert confirmé

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 761
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 761
    Billets dans le blog
    21
    Par défaut
    Citation Envoyé par dacid Voir le message
    En gros, tu me dis de faire du LinqToSql ?
    Pas tout à fait. Je voulais juste te signaler que le benchmark que tu crois faire n'est pas celui que tu fais. Tu penses dans un premier temps mesurer le temps nécessaire pour ta requête SQL alors qu'en réalité, tu peux en exécuter bien d'autres lorsque tu récupères les infos via le DataReader (je ne parle donc pas de tes GetByID).

    L'avantage que je vois avec LinqToSql (ou LinqToEntities, plus puissant, mais plus lourd aussi), c'est de pouvoir réaliser des requêtes fortement typées qui sont validées à la compilation. Je n'ai pas d'exemple sous la main, mais pour pouvoir utiliser LinqToSql c'est relativement simple :
    • Créer un fichier .dbml ("Classes Linq to SQL" dans Visual Studio) ;
    • Ajouter des tables dans le fichier .dbml grâce à l'explorateur de serveur
    • Dans ton code, instancier un objet Context (le nom de la classe va dépendre du nom de ton dbml et est modifiable dans les propriétés) ;
    • Pour chaque table dans ton dbml, tu as une propriété correspondante dans ton objet Context, que tu peux utiliser dans tes requêtes Linq.


    C'est le principe dans les grandes lignes. Trouver un exemple complet ne devrait pas être très compliqué

    Après, il est toujours possible d'utiliser des requêtes classiques à côté, notamment lorsque les requêtes sont très compliquées ou que le besoin de performance est vital (LinqToSql, comme tout ORM a un impact sur les perfs. Il est faible pour des requêtes classiques comme des SELECT avec jointures).

  5. #5
    Membre éprouvé Avatar de dacid
    Homme Profil pro
    Inscrit en
    Juin 2003
    Messages
    1 065
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Juin 2003
    Messages : 1 065
    Par défaut
    Dorinf,

    J'ai un peu avancé depuis tes propos de ton post précédent.
    Ce n'est pas possible pour moi d'utiliser un DBML, j'ai un model déjà trop avancé.

    J'ai mis la suite ici vu que le sujet n'est plus vraiment le même :
    http://www.developpez.net/forums/d15...e/#post8696773

  6. #6
    Expert confirmé

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 761
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 761
    Billets dans le blog
    21
    Par défaut
    Je pense qu'on a surtout besoin d'avoir plus d'information. Avoir la requête exacte que tu exécutes et d'avoir le code pour la construction complète de tes objets.

    J'ai l'impression que parce qu'on parle de LinqToSql, tu penses que c'est cela que tu dois utiliser. Ce n'est pas forcément le cas. Je n'ai fais que pointer un avantage (typage fort => vérification des requêtes à la compilation) et une comparaison avec un DataReader.

  7. #7
    Membre éprouvé Avatar de dacid
    Homme Profil pro
    Inscrit en
    Juin 2003
    Messages
    1 065
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Juin 2003
    Messages : 1 065
    Par défaut
    Dorinf,

    Il n'y a pas tellement plus de code en fait... Voici le contenu de ma boucle complet :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
                items.Add(New tblArticle(_Env) With {
                    .Id = dr("ArtId").ToString.Trim(),
                    .Desc = dr("DES").ToString().Trim(),
                    .Abv = dr("MOTDIR").ToString().Trim(),
                    .Fab = Fabs.GetById(New tblFabricant(_Env) With {.Id = dr("FAB").ToString().Trim()}),
                    .SsFam = SsFams.GetById(New tblArtSsFamille(_Env) With {.Id = dr("SOUFAMART").ToString().Trim(),
                        .Fam = Fams.GetById(New tblArtFamille(_Env) With {.Id = dr("FAMART").ToString().Trim()}),
                        .Nat = Nats.GetById(New tblNature(_Env) With {.Id = dr("NAT").ToString().Trim()})}),
                    .UniStock = Unis.GetById(New tblUnite(_Env) With {.Id = dr("UNISTO").ToString().Trim()}),
                    .UniCommande = Unis.GetById(New tblUnite(_Env) With {.Id = dr("UNICDE").ToString().Trim()}),
                    .UsrMaj = Usrs.GetById(New tblUser(_Env) With {.UserName = dr("UTIMAJ").ToString().Trim()}),
                    .DatMaj = .BddToDate(dr("DATMAJ").ToString()).Value
                })
    Quand à la requête, elle est générée dynamiquement, mais c'est un bête SELECT [...] FROM ART avec un LEFT JOIN sur SOUFAMART, c'est tout.
    Ce qui fait la lenteur est purement le nombre d'enregistrements.

    J'ai testé en ôtant les .BddToDate et Trim(), ca ne change vraiment pas grand chose.
    J'ai testé en mettant juste le .Id = dr("ArtId").ToString.Trim() dans le Add, et là c'est rapide.
    Mais il faut bien que je charge les objects annexe... Et là, même sans le GetById, ça prends du temps.

  8. #8
    Expert confirmé

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 761
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 761
    Billets dans le blog
    21
    Par défaut
    Que contiennent tes constructeurs ?

    Sinon, il est également possible que l'instanciation d'un grand nombre d'objets provoque l'exécution du garbage collector. Et plus il y a d'objets, plus il est lent.

  9. #9
    Membre Expert
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Novembre 2009
    Messages
    2 056
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur .NET

    Informations forums :
    Inscription : Novembre 2009
    Messages : 2 056
    Par défaut
    Pour la bindingList, n'est elle pas utilisee par une grille ou autre contrôle?
    Si oui il faut enlever le lien/passer par une autre instance car le contrôle va se mettre a jour a chaque nouvel enregistrement, ce qui fait perdre beaucoup de temps.

  10. #10
    Membre éprouvé Avatar de dacid
    Homme Profil pro
    Inscrit en
    Juin 2003
    Messages
    1 065
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Juin 2003
    Messages : 1 065
    Par défaut
    Salut Michka,
    Oui, ca y est, je l'ai passée en List, mais a terme, la liste sera en effet utilisée par une grille, je repousse le problème, on verra.
    C'est dommage qu'il n'existe pas d'interrupteurs (comme les .BeginInit() de Devexpress) pour lever les notifications le temps d'un traitement.
    Par contre, il faut que je fasse des tests plus précis, mais ça n'a pas changé grand chose.


    Dorinf,

    En effet, c'est une piste... En fait, ma classe table hérite d'une classe template (qui ne fait pas grand chose).
    Mais en gros, il se passe ça lors de chaque instanciation :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    Classe table:
        Public Sub New(Env As SqiD23.Env)
            MyBase.New(Env)
        End Sub
     
    Classe Template:
        Public Sub New(Env As SqiD23.Env)
            Me.New()
            _Env = Env
            Dim usr As tblUser = CType(Env?.Variables(ENVVAR_USER), tblUser)
            If (usr Is Nothing) Then usr = New tblUser(_Env) With {.UserName = Env?.User?.UserName}
            UsrCre = usr
            UsrMaj = usr
        End Sub
    Il ne se passe pas grand chose, mais la variable "Env" est assez conséquente en fait.... Elle contient toutes les variable et objects utiles pour la globalité du projet.
    Tu penses que ca peut avoir un lourd impact ?

  11. #11
    Expert confirmé

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 761
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 761
    Billets dans le blog
    21
    Par défaut
    As tu essayé d'utiliser le profileur de performance de visual studio afin de regarder ce qui prend du temps ? Si non, ce serait une bonne idée de le faire.

  12. #12
    Membre éprouvé Avatar de dacid
    Homme Profil pro
    Inscrit en
    Juin 2003
    Messages
    1 065
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Juin 2003
    Messages : 1 065
    Par défaut
    Pour info,
    J'ai essayé avec les entities :
    11:40. Articles chargés: Debut(40:12) SqlExec(40:26) Fin(40:44, 0 elmts).

    Avec une DataTable :
    10:32. Articles chargés: Debut(31:31) SqlExec(31:39) Fin(32:00, 362113 elmts).

    Avec les entities et En ôtant les _env dans les constructeurs:
    10:53. Articles chargés: Debut(53:24) SqlExec(53:37) Fin(53:51, 0 elmts).

    Bref, quoi que je fasse, je n'arrive pas à descendre sous les 25 secondes au total.
    Je jette l'éponge... Je vais m'orienter vers du Multi-Threading plutôt

    Merci beaucoup pour tes lumières.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 2
    Dernier message: 19/11/2015, 15h55
  2. Optimisation requête XML - LinQ
    Par jeyGey dans le forum Linq
    Réponses: 2
    Dernier message: 25/09/2014, 16h57
  3. Optimisation d'utilisation LINQ to XML
    Par Invité dans le forum C#
    Réponses: 0
    Dernier message: 04/02/2013, 10h28
  4. Linq - question optimisation requête
    Par boby62423 dans le forum Linq
    Réponses: 2
    Dernier message: 07/04/2009, 18h19
  5. [langage] Optimiser la lecture d'un fichier
    Par And_the_problem_is dans le forum Langage
    Réponses: 2
    Dernier message: 11/06/2002, 10h24

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo