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

  1. #1
    Membre du Club Avatar de Takumi
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2009
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    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
    Points : 62
    Points
    62
    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 éprouvé
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2007
    Messages
    693
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Consultant informatique

    Informations forums :
    Inscription : Juillet 2007
    Messages : 693
    Points : 1 187
    Points
    1 187
    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 du Club Avatar de Takumi
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2009
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    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
    Points : 62
    Points
    62
    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 éprouvé
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2007
    Messages
    693
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Consultant informatique

    Informations forums :
    Inscription : Juillet 2007
    Messages : 693
    Points : 1 187
    Points
    1 187
    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 du Club Avatar de Takumi
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2009
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    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
    Points : 62
    Points
    62
    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 éprouvé
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2007
    Messages
    693
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Consultant informatique

    Informations forums :
    Inscription : Juillet 2007
    Messages : 693
    Points : 1 187
    Points
    1 187
    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 ?

  7. #7
    Membre du Club Avatar de Takumi
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2009
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    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
    Points : 62
    Points
    62
    Par défaut
    En faite pour conserver je suis partis sur un truc pas trop compliqué. L'utilisateur entre la lettre "a", je garde dans mon dictionnaire à la clef "a" la liste filtré correspondante, il tape ensuite "ab", je recherche d'abord "a", en extrait la liste et filtre puis garde la clef "ab" avec la liste correspondante. Et ainsi de suite. Après il y a quelques régles particulières, par exemple si la TextBox devient finalement vide, je clear le dictionnaire pour ne plus avoir aucun référence vers les listes filtré et hop poubelle quand GC passe dans le coin. Il y aussi le cas où l'utilisateur passe de "ab" à "po" par exemple, je compare alors la première lettre, si mon dico ne contient pas de mot commençant par cette lettre j'estime que l'utilisateur a changé de recherche, alors là je clear le dico et recommence une pile. En faite je vais faire en sorte de n'avoir dans mon dico que des recherches qui se suivent. Par exemple les clefs "a", "ab","abb","abba", mais pas "a","ab","po","pol". Le but est ici de garder en mémoire les résultats de recherche pour des chaines qui se suivent, c'est à usage limité et n'a pas pour but de garder le résultat de toutes les recherches le temps de la durée de vie de l'application. Je trouvais ça particulièrement utile dans le cas où l'utilisateur va surtout effacer des lettres par exemple, je n'aurais qu'a rechercher la clef précédente et sortir de suite la liste filtré. C'est vrai que ça demanderas un peu plus de boulot à coder, devoir faire un peu de traitement sur des chaines et collection de chaines pour vérifier ce que j'ai expliqué avant mais je pense que le gain est pas négligeable. Si j'ai pas encore donné de code, c'est parce que justement j'ai pas encore finis cela, mais promis dans le prochain poste j'en mettrais.

    Sinon j'utilise .NET 4, j'essais de rester au maximum à jours, et comme j'ai pas de bout de code très spécifique à une version cela ne pose pas trop de problème pour migrer.

    Pour ma source de donnée, j'ai pas encore vraiment réfléchis à cela, c'est une chose que j'ai pas encore développé. Vu que je vais sauvegarder une bibliothèque de chanson, puis des playlists je pensais partir sur du XML qui serait chargé en mémoire au démarrage de l'application. Pour le moment j'ai fait le gros porc en remplissant ma collection d'objet avec un for.

  8. #8
    Membre du Club Avatar de Takumi
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2009
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    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
    Points : 62
    Points
    62
    Par défaut
    Bonsoir,

    J'ai pris quelques minutes pour faire 2-3 test en remplacant quelques instructions LINQ par une boucle for. Et je doit dire que je suis assez déçu, et je n'y comprends rien aussi.

    J'ai voulus d'abord chronométrer le temps que mettait les instructions GroupBy a s'executer:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    NewFilteredPlaylist.SongCount = FilteredList.Count();
    NewFilteredPlaylist.AlbumCount = FilteredList.GroupBy(c => c.AlbumTitle, StringComparer.OrdinalIgnoreCase).Count();
    NewFilteredPlaylist.ArtistCount = FilteredList.GroupBy(c => c.Author, StringComparer.OrdinalIgnoreCase).Count();
    J'ai obtenus des temps qui allait entre 6-13 ms pour une collection de 3000 éléments environ.

    Ensuite je tente un GroupBy "maison" avec une boucle for:

    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
     
    private void GetPlaylistCountResult(List<Song> SongList, ref FilteredPlaylist FilteredPlaylistData)
    {
    <div style="margin-left:40px">int Count = SongList.Count;
    List<String> ArtistList = new List<string>(), AlbumList = new List<string>();
     
    for (int i = 0; i < Count; i++)
    {
         if (!ArtistList.Contains(SongList[i].Author))
              ArtistList.Add(SongList[i].Author);
     
          if (!AlbumList.Contains(SongList[i].AlbumTitle))
              AlbumList.Add(SongList[i].AlbumTitle);
    }
     
    FilteredPlaylistData.AlbumCount = AlbumList.Count;
    FilteredPlaylistData.ArtistCount = ArtistList.Count;
    FilteredPlaylistData.SongCount = SongList.Count;</div>}
    Et c'est là que les choses étranges ont commencé. Au début j'obtenais des temps qui était entre 0-5 ms. J'était content, je fait une ou deux modifs par ci, par là, et là je n'arrive plus à obtenir les mêmes temps. Maintenant je suis entre 200-350ms pour la boucle for. Je suis assez déçus car finalement LINQ va bien plus vite. Ça me parait tout de même étrange mais je n'arrive pas à voir ce que j'ai pu merder. C'est vraiment étrange que un seul tour de boucle soit aussi long que plusieurs tours de collections. Pour chronométrer j'ai utiliser un StopWatch que je démarre avant l'appel de fonction et que je termine après.

    Je crois que ça m'a calmé pour ce soir, je retenterais demain maintenant.

    PS: J'ai retenté ce matin quelques petits test. Et il semble que ce sont les List.Contains dans ma boucle for qui prenne tous ce temps. Au bout d'un moment si par exemple j'ai une liste de 3000 chansons chacun ayant un artiste différent, j'ai une List<String> qui va avoisiner les 3000 string parcourus à chaque tour de boucle.

  9. #9
    Membre régulier
    Inscrit en
    Octobre 2005
    Messages
    62
    Détails du profil
    Informations forums :
    Inscription : Octobre 2005
    Messages : 62
    Points : 85
    Points
    85
    Par défaut
    Chercher à faire des roues plus rondes, ça ne mène jamais très loin.
    LINQ utilise un index sur le hash du comparer utilisé, là où toi tu fait un List.Contains qui va scanner depuis le début de la liste.

    Tu devrais en dire plus sur ton code, pour trouver ce qui est vraiment long.

  10. #10
    Membre du Club Avatar de Takumi
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2009
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    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
    Points : 62
    Points
    62
    Par défaut
    Bonjour,

    Voila j'ai un petit peu avancé sur mon code, j'ai tout d'abord voulus avancer mon idée de garder en mémoire les précédents filtres en mémoire afin de soit refiltrer dessus dans le cas ou la chaîne de caractères entrée suit la précédente, ou bien tous simplement extraire la liste filtrée dans le cas où l'utilisateur efface la chaîne. Je montre d'abord le code, et je m'explique après:

    Fonction appelé à chaque fois que la propriété Text de la TextBox change:
    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
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
     
    private void FilterPlaylist()
    {
    	string Filter = this._CurrentFilter;
     
    	// si la chaine n'est pas vide, on doit filtrer
    	if (Filter != string.Empty)
    	{
    		//si la chaine n'a qu'un caractère, on efface tous les filtres précédent, puis on filtre
    		if (Filter.Length == 1)
    		{
    			FilteredPlaylist NewFilteredPlaylist = this.ExtractSubPlaylist(this._SongList, Filter);
     
    			this._FilterResult.Clear();
    			this._FilterResult.Add(Filter, NewFilteredPlaylist);
    			this._SongListView.Source = NewFilteredPlaylist.SongList;
    		}
    		else //sinon on recherche si le filtre n'est pas déjà existant
    		{
    			bool FirstLetterInDico = this._FilterResult.ContainsKey(Filter[0].ToString());
     
    			/*si la première lettre de la chaîne a déjà été filtré, on cherche si la chaîne complète
    			n'aurait pas déjà été filtré*/
    			if (FirstLetterInDico)
    			{
    				string DicoKey = Filter;
    				IEnumerable<Song> SongListToUse;
    				bool IsInDico = this._FilterResult.ContainsKey(Filter);
     
    				/*si la chaîne n'existe pas, on tente de chercher les chaines gardé en mémoire qui s'en
    				rapproche le plus*/
    				if (!IsInDico)
    				{
    					int i;
    					bool HasNeighbor = false;
    					FilteredPlaylist NewFilteredPlaylist;
     
    					for (i = Filter.Length - 1; i > 1; i--)
    					{
    						HasNeighbor = this._FilterResult.ContainsKey(Filter.Substring(0, i));
    						if (HasNeighbor)
    							break;
    					}
     
    					if (HasNeighbor)
    						DicoKey = Filter.Substring(0, i);
    					else
    						DicoKey = Filter[0].ToString();
     
    					NewFilteredPlaylist = this.ExtractSubPlaylist(this._FilterResult[DicoKey].SongList, Filter);
    					this._FilterResult.Add(Filter, NewFilteredPlaylist);
    					SongListToUse = NewFilteredPlaylist.SongList;
    				}
    				else // sinon on extrait la liste filtré
    				{
    					if(this._FilterResult.ContainsKey(this._PreviousFilter))
    						this._FilterResult.Remove(this._PreviousFilter);
     
    					SongListToUse = this._FilterResult[Filter].SongList;
    				}
     
    				//on bind la liste à la CollectionView
    				this._SongListView.Source = SongListToUse;
    			}
    			else // sinon cela signifie que l'on commence une nouvelle chaîne, alors on filtre au minimum la première lettre puis la chaîne complète
    			{
    				FilteredPlaylist StartFilteredPlaylist = this.ExtractSubPlaylist(this._OnePlaylist, Filter[0].ToString());
    				FilteredPlaylist NewFilteredPlaylist = this.ExtractSubPlaylist(StartFilteredPlaylist.SongList, Filter);
    				this._FilterResult.Clear();
    				this._FilterResult.Add(Filter[0].ToString(), StartFilteredPlaylist);
    				this._FilterResult.Add(Filter, NewFilteredPlaylist);
    				this._SongListView.Source = NewFilteredPlaylist.SongList;
    			}
    		}
    	}
    	else //sinon on remet la liste complète des chanson
    	{
    		this._FilterResult.Clear();
    		this._SongListView.Source = this._SongList;
    	}
     
    	this._PreviousFilter = Filter;
    }
    Fonction qui filtre:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    private FilteredPlaylist ExtractSubPlaylist(IEnumerable<Song> Playlist, string Filter)
            {
                List<Song> FilteredList;
                FilteredPlaylist NewFilteredPlaylist = new FilteredPlaylist();
     
                FilteredList = Playlist.Where(c => Regex.IsMatch(c.Title, Filter, RegexOptions.IgnoreCase)).ToList();
                NewFilteredPlaylist.SongList = FilteredList;
                NewFilteredPlaylist.SongCount = FilteredList.Count();
                NewFilteredPlaylist.AlbumCount = FilteredList.GroupBy(c => c.AlbumTitle, StringComparer.OrdinalIgnoreCase).Count();
                NewFilteredPlaylist.ArtistCount = FilteredList.GroupBy(c => c.Author, StringComparer.OrdinalIgnoreCase).Count();
     
                return NewFilteredPlaylist;
            }
    Structure représentant les résultat d'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;
        }
    Propriété privée de ma ViewModel (utilisé dans les fonctions précédentes)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    private ObservableCollection<Song> _SongList;
    private Dictionnary<string,FilteredPlaylist> _FilterResult;
    private string _CurrentFilter, _PreviousFilter;
    Voila ce que j'ai fait pour le moment. C'est pas encore optimale, il manque encore quelques cas de figure qui ne sont pas traité, mais j'ai pas eu le temps de faire plus pour le moment. J'ai un peu honte, plus je revois mon code, plus je trouve que c'est un bordel sans nom.

    Quoi qu'il en soit, j'ai finalement laissé LINQ pour faire mes GroupBy et autres joyeuseté sur les collections car il est bien plus rapide qu'une boucle for dans mon cas. J'ai fait quelques calculs. Quand je filtre une collection le traitement complet de la fonction "FilterPlaylist" met entre 20-40ms. Avec une boucle for 200-350ms. Par contre quand un filtre est déjà existant, il ne fait que le récupérer et je doit compter environ 0-10ms pour effectuer tous le traitement. Au vu des temps très court que cela demande, je pense finalement que les problèmes de ralentissement ne viennent pas forcement de LINQ ou des différents traitement que je fais. Une chose que je me demande est, est-ce que cela pourrait venir de ma ListView. A chaque fois que je fait un filtrage, je change la source de ma CollectionViewSource, et celle-ci est bindé sur la propriété DataContext de la ListView, donc à chaque changement la ListView doit recréer tous les ListItem. J'ai si je me souviens bien de mon code activé la Virtualisation pour voir si cela améliorerais les choses, mais je crois n'avoir vu pas de grand changement (peut être ai-je oublié de faire un truc pour que cela marche vraiment).

    Finalement je ne pense pas que faire le fitrage sur un autre Thread, améliore vraiment les performance, mais peut être que je me trompe. J'attends maintenant vos retour.

  11. #11
    Membre du Club Avatar de Takumi
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2009
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    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
    Points : 62
    Points
    62
    Par défaut
    Bonsoir,

    J'ai finalement refait un test simple. J'ai laissé tous le traitement lié à LINQ et la recherche de playlist déjà existante, et j'ai commenté les lignes de code qui change la Source de mon CollectionViewSource. Et là c'est assez fluide, quasiment pas de ralentissement. Donc le problème de ralentissement semble venir quand la ListView doit recharger la nouvelle collection qui lui est donné par CollectionViewSource. J'avais activé la Virtualisation pour espérer gagner un peu, mais ça ne semble rien changer. Sur le coup là je vois pas comment je peux optimiser ça pour que cela soit plus rapide. Si vous avez une idée ou quoi que ce soit qui marche, je suis preneur.

    Je vous remercie d'avance.

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