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

Windows Presentation Foundation Discussion :

Perte de performance avec un Filtrage en temps réel


Sujet :

Windows Presentation Foundation

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé Avatar de Takumi
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2009
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Juin 2009
    Messages : 163
    Par défaut Perte de performance avec un Filtrage en temps réel
    Bonjour,

    Je souhaite dans mon application pouvoir filtrer une collection selon une entrée utilisateur. J'utilise actuellement une CollectionViewSource comme source de donnée d'un ListView. A cette CollectionViewSource est lié une ObservableCollection<Song> où Song est une classe que j'ai crée. J'ai vu qu'il n'était pas possible de filtrer les données avec le CollectionViewSource, cependant j'ai pu voir sur MSDN qu'il existe une ListCollectionView avec une propriété Filter. C'est bien mais cela ne résout pas tous mes problèmes.

    En effet je souhaite pouvoir récupérer des informations sur ce qui a été filtrer. La collection contient en effet des objet de type Song, et je souhaite pouvoir par exemple compter le nombre d'artiste concerné par le filtrage de ma collection de Song, le nombre d'album que cela représente. Et malheureusement je ne vois pas de propriété qui permettent de récupérer la liste filtrer afin que par la suite justement je puisse faire les traitements appropriés pour avoir les résultat. De plus je ne souhaite pas me limiter à ces opérations, par exemple, je souhaite que quand un utilisateur lance une chanson (en double cliquant sur une des chansons de la liste) ce n'est que la collection filtrer qui soit lu. Donc mon besoin de filtrer n'est pas que visuel, il faut que par la suite je puisse récupérer cette liste filtrée puis la distribuer à mes différents models qui en auront besoin.

    Donc pour le moment je me suis limité à faire le filtre moi même, en utilisant LINQ, je commence d'abord par faire un where(). Je me retrouve alors avec une collection filtrée, ensuite pour connaitre les différentes count nécessaire, je fais par exemple un regroupement suivi d'un count (par exemple pour connaitre le nombre d'artiste dans le collection filtrée: GroupyBy(c => c.Artist).Count()). Et cela deux fois, une fois pour le nombre d'artiste et une autre pour le nombre d'album. Je me retrouve alors avec les informations que je souhaite, et une collection que je peux manipuler et transmettre. Ensuite ce que je fait, c'est que je crée une nouvelle ObservableCollection qui reçoit en paramètre pour son constructeur ma liste filtrée, et cette Observable va être assigné à la source de ma CollectionView. Donc au final ça fait quand même beaucoup de bouclage sur mes collections.

    Maintenant ma contrainte est que pour filtrer j'attends une entrée utilisateur, pour cela j'ai mis une TextBox et c'est tout. Le filtrage doit pouvoir se faire en "temps réel". Pour cela j'ai par exemple dans mon ViewModel associé une propriété string "CurrentFilter" à la propriété Text du TextBox, donc dès que Text change CurrentFilter aussi puis j'enclenche ma fonction de filtrage juste derrière. Maintenant ce qui se passe c'est quand je souhaite entrer très rapidement quelque chose dedans, ou encore effacer beaucoup de lettre en maintenant appuyé la touche retour, ça "rame" pas mal. J'ai fait des test sur une collection ne contenant que 3000 éléments, mais il se pourrait qu'il y en ai bien plus et alors le résultat serait encore plus dramatique. J'ai tenté de trouver des petites astuces, d'abord activer la Virtualisation sur ma ListView. Puis si le texte entré contient juste une lettre de plus (par exemple: Abb devient Abba), je réutilise la précédente liste filtré pour ne pas consulter toute la collection de chanson entière.

    Pour ce projet personnel, je tente de "recopier" une autre application existante, et sur cette application cette fonctionnalité est très fluide. Après c'est très certainement pas le même langage, pas le même Framework, pas la même logique derrière. Mais ce serait dommage que je ne puisse réussir a obtenir le même résultat avec du C# et .NET. Donc je me retrouve à ne pas vraiment savoir d'où le problème de performance peut venir, est-ce ma machine (un vieux Athlon 64 3000+), mon code qui filtre qui est mal pensé, le langage ou les Control qui ne permettent pas d'avoir une exécution suffisamment rapide. Je ne savais pas dans quelle section poster cela, et je me suis dit que WPF serait pas mal vu que mon appli est faite là dessus.

    Je suis désolé de ne pas pouvoir poster de code, je suis au bureau et je n'ai pas le projet sous la main. Je sais que ça va pas forcement aider à m'aider, mais je me dit qu'en exposant déjà mon problème, peut être avez-vous déjà des idées, astuces, conseils à donner, comme ça en rentrant ce soir je pourrais éventuellement tester quelque chose. J'espère avoir été claire dans mes explications.

    Je vous remercie d'avance pour votre aide.

  2. #2
    Membre émérite
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2007
    Messages
    693
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Consultant informatique

    Informations forums :
    Inscription : Juillet 2007
    Messages : 693
    Par défaut
    Bonjour,

    Comme tu dis
    Donc au final ça fait quand même beaucoup de bouclage sur mes collections
    , effectivement les
    GroupyBy(c => c.Artist).Count())
    successifs te coûtent très certainement beaucoup.

    Linq est pour moi dangereux car son écriture "cool" pour le développeur lui fait souvent oublié la logique qu'il y a derrière.

    Pour ces groupby, je te suggère donc de retourner vers la bonne vieille boucle for qui a fait ses preuves et qui te permettra de calculer tous tes groupby en parcourant qu'une seule fois ta liste.

    Pour ce qui est du filtrage du plus en plus précis, tu as eu totalement raison de filtrer sur ta liste déjà filtrée, les éléments de toute façon ininterressants ne seront plus à parcourir !

    Ensuite, il y a toujours la piste d'exécuter la recherche sur un autre thread que le thread UI pour éviter de le bloquer.

    Voici pour un début de pistes avec les éléments donnés

  3. #3
    Membre confirmé Avatar de Takumi
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2009
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Juin 2009
    Messages : 163
    Par défaut
    Bonsoir,

    Merci pour ta réponse, cela me donne déjà un début de piste à explorer.

    Sur ce que tu m'as dit, faire passer ce traitement sur un thread autre que le thread UI pourrait très certainement me permettre d'avoir une interface fluide et réactive comme je le souhaite.

    J'ai tenté d'utiliser les threads. Première chose, je ne l'avais jamais fait avant (enfin si une fois sur une appli mais c'était un truc assez simple, qui ne nécessitait pas d'avoir accés aux propriété privés de ma classe, ni de devoir mettre à jours les contrôles,...), donc là j'ai pas mal merdé. En faite dans la fonction que j'ai écrit pour filtrer ma collection, j'ai voulus tenter de garder en mémoire les filtrages précédent. Je m'explique; j'ai une structure qui me permet de garder les informations sur un filtrage:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    internal struct FilteredPlaylist
    {
        public IEnumerable<Song> SongList;
        public int SongCount, AlbumCount, ArtistCount;
    }
    Ensuite dans ma classe ViewModel, j'ai une propriété privé de type Dictionnary<string, FilteredPlaylist>. Par exemple, je tape la lettre "a" dans ma textbox, à ce moment ma fonction va vérifier si il y a une clef qui correspond à "a" dans le dictionnaire que j'ai cité précédamment, si oui je n'ai qu'a sortir la valeur associé qui est de type FilteredPlaylist, j'en extrait la liste de chanson et l'assigne à la propriété Source de la ListCollectionView. Dans le cas où il n'y a pas de clef existante, je filtre ma liste de chanson. Maintenant je tape la lettre "b", je me retrouve avec la chaîne suivante "ab", même scénario sauf que je parcours la collection de clef à la recherche soit de la clef correspondante, soit une qui s'en rapproche, j'essais de faire une sorte de pile qui se remplit au fur et à mesure qu'on ajoute des lettres ou qui dépile quand on en efface une. La première lettre de la chaîne est importante, car si finalement j'écrit "ba" au lieu de "ab", j'efface tout le dictionnaire pour recommencer la pile. Je rentrer pas dans les détails car long à expliquer, moins parlant sans extrait de code, mais voila l'idée générale d'un truc que je tente actuellement pour éviter au maximum d'avoir à parcourir le moins de collection possible. Tout ça pour en revenir à mon histoire de thread, pour dire que si je tente d'effacer trop vite (en maintenant appuyé la touche retour), plusieurs sont alors lancés "quasi en même temps", et tente d'accéder au dictionnaire et là plein de problème de clef existante quand je tente d'en ajouter, enfin bref je suis pas encore assez calé niveau mutli-threading pour faire quelque chose de correcte.

    Je vais donc dans un premier temps me tourner vers ta première suggestions qui est de faire mes différents count dans une boucle for, plutôt que de passer par LINQ. Par contre j'ai une petite question, je voudrais essayer d'optimiser le truc au maximum pour que ce soit le plus rapide possible.

    Ici j'ai besoin de faire des group by, je parcours donc ma liste de chanson filtrer, et ma question est quel est le moyen pas trop gourmand pour y arriver? Est-ce que je peux par exemple avoir des List<string>, une pour chaque count que j'ai à faire (le nombre d'artiste total dans la collection filtrée, le nombre d'album total dans la collection filtrée), et à chaque passage dans ma boucle je test si il y a déjà une valeur existante dans la liste, si oui je fais rien, si non je l'ajoute et à la fin je n'aurais qu'a récupérer la propriété Count de mes listes? Est-ce qu'il y a encore meilleur (chose très certainement sûr)?

    Je suis désolé de pas ne pas encore poster de code, mais j'ai pas quelque chose de présentable à force de tenter quelque petite chose. J'ai parfois l'impression même d'avoir pas mal compliqué la chose pour au final pas énormément d'amélioration. Je suis à l'écoute de tous conseils.

    Merci d'avance pour l'aide.

  4. #4
    Membre émérite
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2007
    Messages
    693
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Consultant informatique

    Informations forums :
    Inscription : Juillet 2007
    Messages : 693
    Par défaut
    Pour ce qui est des groupby, voilà comment je vois les choses (en code pas compilable tel quel) :
    On suppose qu'on a une classe Chanson :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class Chanson {
    public string A {get;set;}
    public string B {get;set;}
    public string C {get;set;}
    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
     
    const string FORMAT_KEY_AC = "{0}_{1}";
    List<string> groupbyA = new List<string>(), groupbyB = new List<string>(), groupbyAC = new List<string>();
     
    for(int i = 0; i < maListedeChansons.Count; i++)
    {
          if(!groupbyA.Contains(maListedeChansons[i].A))
              groupbyA.Add(maListedeChansons[i].A);
     
          if(!groupbyB.Contains(maListedeChansons[i].B))
              groupbyB.Add(maListedeChansons[i].B);
     
          string keyAC = string.Format(FORMAT_KEY_AC, maListedeChansons[i].A, maListedeChansons[i].C);
          if(!groupbyAC.Contains(keyAC))
              groupbyAC.Add(keyAC);
    }
    Ensuite effectivement avec les Count sur chaque liste tu as le nombre de résultats différents pour chaque groupby.

    Donc sur ce point je pense qu'on converge (mais un peu de code fait pas de mal)

    Ah si un truc auquel je pense, il existe des outils pour monitorer ton application comme dotTrace, ANTS Performance Profiler. Personnellement, j'ai pas mal utilisé le second (certes payant mais la version de démo dure 14 jours). Je trouve que cet outil met facilement en évidence les temps d'exécution et permet au développeur de voir et d'apprendre ce qui coute beaucoup et ce qui coute peu.

  5. #5
    Membre confirmé Avatar de Takumi
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2009
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Juin 2009
    Messages : 163
    Par défaut
    Bonjour,

    Merci pour ta réponse. Sur le GroupBy nos idées converges, je penses que je verrais naître les premières lignes de codes ce soir quand je serait rentré chez moi et je posterais.

    Sinon par rapport à mon idée de conserver les résultats précédents au fur et à mesure que l'utilisateur tape quelque chose. Pense-tu que c'est une idée viable? Ou bien je me complique trop la tâche pour finalement pas grand chose de plus?

    Et concernant les outils de performance, j'avais testé il y a 2 semaines de cela les outils de RedGate (ANTS Performance et ANTS Memory), mais la période d'essai est terminée. Et j'ai pas trop envie de donner 800€+ pour avoir des outils que je n'utilise que sur un projet perso. Mais il est vrai qu'il sont extrêmement efficace.

  6. #6
    Membre émérite
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2007
    Messages
    693
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Consultant informatique

    Informations forums :
    Inscription : Juillet 2007
    Messages : 693
    Par défaut
    Pour moi, conserver les précédents filtrages peut être intéressants mais attention à ne pas trop en conserver sinon tu risques d'exploser la mémoire utilisée. Quoique pour éviter cela tu peux utiliser les WeakReference (c'est assez intéressant).
    En gros, il te faut un cache de recherche, donc regarde du coté des frameworks de cache(ou développe le tien) (EntLib, ...)
    Mais il faut bien faire la distinction entre "sauvegarder les précédentes recherches" pour retrouver plus vite les résultats et "préciser une recherche" (cas de la recherche sur "a" puis sur "ab")

    Sinon personnellement, pour optimiser j'ai pendant longtemps fait des petits programmes de test pour démontrer l'efficacité de telle ou telle syntaxe, n'hésite pas à le faire de ton coté.

    Un exemple, tu peux comparer le LINQ Where que tu utilises pour le filtrage et le FindAll, puis un foreach et enfin un for(int ...).

    Sinon tu disais que ton appli est en WPF, mais sur quel framework ? 3.5 ? 4 ?

    N'hésite pas à montrer un peu de code quand tu auras quelque chose de montrable .

    Edit : PS j'adore les sujets d'optimisation
    Oh il me semble qu'on a oublié de parler d'un sujet important : quelle est ta source de données ? une BDD ? un stockage fichier perso ? autre chose ?

Discussions similaires

  1. Perte de performance avec PgPool, une idée ?
    Par vil-farfadet dans le forum Outils
    Réponses: 2
    Dernier message: 19/09/2012, 12h09
  2. Recherche avec filtrage en temps réel
    Par reitsab dans le forum WinDev
    Réponses: 5
    Dernier message: 24/11/2009, 17h06
  3. estimation pertes de performance avec URL Rewriting ?
    Par clavier12AZQSWX dans le forum Apache
    Réponses: 1
    Dernier message: 12/04/2009, 22h43
  4. Pb avec Horloge système CMOS/temps réel
    Par bbkenny dans le forum Windows 2000/Me/98/95
    Réponses: 1
    Dernier message: 12/12/2008, 21h45
  5. Réponses: 14
    Dernier message: 11/06/2008, 09h21

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