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 :

Architecture de mon algorithme: quand séparer un traitement dans un thread ?


Sujet :

Windows Presentation Foundation

  1. #21
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    Citation Envoyé par billybobbonnet Voir le message
    - J'ai minuté au sein d'un lot la différence entre un for et un parallel.for, et le resultat est surprenant: environ 30% plus lent avec le parallel for.
    Ce qui prouve qu'il y a contention quelque part mais impossible de dire où a priori. Il faut que tu conduises des tests pour le déterminer. Par exemple tu pourrais mettre en pause le déboguage (debug > break) pour examiner ce que fait chaque thread à ce moment (debug > windows > threads ou quelque chose comme ça). Tu pourrais aussi simplifier petit à petit ton code (mettre des parties en commentaires) et voir si les performances augmentent. Ou conduire des tests plus simples sur l'efficacité de SQLite face à des requêtes concurrentes.

    Au passage tu avais parlé plus tôt de quelques dizaines de Mo et j'avais alors dit que si les lectures pouvaient tenir dans le cache mémoire elles devraient être bien parallélisables. Confirmes-tu que ta base de données tient entièrement en mémoire ? Si ce n'est pas le cas on en revient au problème du disque qui pourrait être le goulet d'étranglement.

    Je suis étoné de voir que les threads semblent partager les variables des fonctions qu'ils appellent.
    Les champs statiques (et les champs des instances partagées) sont effectivement partagées. En revanche c'est une affaire de champs/variables/états, pas de méthodes. Il ne sert à rien de déplacer les méthodes d'un type vers un autre.

    Tu as deux stratégies possibles : partager de façon sûre ou bien isoler.

    La première requière une forme de synchronisation (en général via "lock") autour des accès aux états partagés. La seconde nécessite d'utiliser des instances différentes ayant chacune leurs propres états. Toutefois tu peux utiliser un truc pour les champs statiques : appliquer l’attribut ThreadStaticAttribute à ces champs, cela forcera le JIT à créer en fait un champ par thread. L'accès à ces champs sera toutefois plus lent.

    Des trois solutions que tu avais proposées, seule la seconde fonctionnerait donc.



    Note aussi que les états partagés sans précautions peuvent être corrompus : si deux threads font x++, parfois x ne sera augmenté que de 1 et non de deux (voir ma réponse à youtpout978 le 30/9). D'où l'importance de la synchronisation autour de ces accès.

    Pardonnez la naïveté de la question, c'est la première fois que je me frotte à ce genre de choses.
    Je t'en prie, inutile de t'excuser. Par ailleurs c'est un sujet complexe et il est normal que tu sois encore ignorant de ces problèmes si tu n'as pas reçu de formation formelle sur ces sujets (ce qui est généralement le cas dans les formations courtes +2/+3 faute de temps).

    Citation Envoyé par billybobbonnet Voir le message
    C'est possible à l'aide de la fonction datatable.ImportRow à condition que le schéma soit compatible, ce qui est faisable en faisant comme il suit:
    Merci pour l'astuce.

  2. #22
    Membre du Club
    Inscrit en
    Mars 2009
    Messages
    104
    Détails du profil
    Informations forums :
    Inscription : Mars 2009
    Messages : 104
    Points : 69
    Points
    69
    Par défaut
    Au passage tu avais parlé plus tôt de quelques dizaines de Mo et j'avais alors dit que si les lectures pouvaient tenir dans le cache mémoire elles devraient être bien parallélisables. Confirmes-tu que ta base de données tient entièrement en mémoire ? Si ce n'est pas le cas on en revient au problème du disque qui pourrait être le goulet d'étranglement.
    je confirme. Jusqu'à la récente modification pour intégrer l'insert SQLite final, tout était dans des datatables en mémoire. Par ailleurs, la partie que j'ai mis avec le parallel.for est celle qui traite chaque item à l'intérieur des lots, du coup elle n'écrit rien et n'accède aux datatables qu'en lecture. La partie qui fait l'insert SQLite est appelée en fin de lot, à l'extérieur du parallel.for.

    Je vais opter pour ma seconde solution, et au passage j'essaierai de mettre en place un lock sur la routine finale d'insert SQLite pour mettre en pratique ce que je crois avoir compris. Si c'est plus lent, j'aurais au moins appris un truc et je n'aurai qu'à intégrer l'insert dans mon objet.

    Merci pour ta réponse. J'ai hâte de comparer les temps d'exécution entre la version parallélisée et la version séquentielle. Je vous tiendrai au courant des résultats.

  3. #23
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    Citation Envoyé par billybobbonnet Voir le message
    je confirme. Jusqu'à la récente modification pour intégrer l'insert SQLite final, tout était dans des datatables en mémoire. Par ailleurs, la partie que j'ai mis avec le parallel.for est celle qui traite chaque item à l'intérieur des lots, du coup elle n'écrit rien et n'accède aux datatables qu'en lecture. La partie qui fait l'insert SQLite est appelée en fin de lot, à l'extérieur du parallel.for.
    As-tu essayé de carrément supprimer les écritures ? Elles pourraient interférer avec les lectures, auquel cas il serait intéressant de les faire une fois tous les lots traités. Je serais curieux que tu fasses le test. Dans le même genre un profilage de l'appli serait bien venu.

    Si malgré ça les lectures ne deviennent pas plus rapides, alors c'est anormal. Peut-être le cache de SQLite est-il limité ? Un problème de configuration ?

    Parce que des lectures parallèles qui tiennent en mémoire doivent être efficacement parallélisables, sinon il y a un gros problème quelque part.

    Petite vérification : ton processeur a bien plusieurs coeurs ? Je parle de vrais coeurs, sans compter l'hyperthreading.

    j'essaierai de mettre en place un lock sur la routine finale d'insert SQLite pour mettre en pratique ce que je crois avoir compris.
    A moins que ça ait bouleversé les opérations à traiter en rajoutant du travail, isoler les états n'affecte que la consistance des opérations, pas la vitesse.

  4. #24
    Expert confirmé

    Homme Profil pro
    Développeur .NET
    Inscrit en
    Novembre 2010
    Messages
    2 065
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur .NET

    Informations forums :
    Inscription : Novembre 2010
    Messages : 2 065
    Points : 4 229
    Points
    4 229
    Par défaut
    Pourquoi utiliser à tout prix des datatables et non des listes typées ?

  5. #25
    Membre du Club
    Inscrit en
    Mars 2009
    Messages
    104
    Détails du profil
    Informations forums :
    Inscription : Mars 2009
    Messages : 104
    Points : 69
    Points
    69
    Par défaut
    As-tu essayé de carrément supprimer les écritures ? Elles pourraient interférer avec les lectures, auquel cas il serait intéressant de les faire une fois tous les lots traités
    Sauf si on s'est mal compris, c'est déjà ce que je fais. Je n'écris rien sur le disque avant la fin du traitement d'un lot, hors du parallel.for. Chaque lot a sa propre datatable (en mémoire) qui lui est passée lors de son appel. il travaille dessus tout le long et ne produit une autre datatable qu'en fin de traitement, pour produire l'argument de la routine d'insert SQLite.

    Pour reprendre les grandes étapes de l'algo:

    - Une phase préparatoire séquentielle et bien optimisée me remplit une datatable.
    - Une phase d'analyse de chaque lot. Un lot correspond à plusieurs lignes de la datatable avec un ID de lot commun. Ces lignes sont passées en argument à la procédure d'analyse sous la forme d'une datatable.
    - Dans la phase d'analyse de chaque lot, il y a deux grandes parties:
    * je traite les items un par un et je remplis des cellules de la datatable du lot. C'est une écriture en mémoire, mais pas sur le disque
    * je traite l'ensemble des items qui se co-déterminent par leurs propriétés, suite à de nombreuses tâches de comparaison, manipulation de string, regex, etc. puis je fais un insert SQlite. C'est cette partie qui est la plus longue, avec ou sans insert.

    Juste pour distinguer les deux choses:
    -Je compte paralléliser le traitement des lots les enfilant tous dans le threadpool
    - Le parallel.for est juste une tentative, à l'intérieur du traitement d'un lot, de paralléliser le traitement des items. Les tests que j'ai fait étaient sur une version où les lots étaient traités de façon séquentielle.


    Dans le même genre un profilage de l'appli serait bien venu.
    je pense y venir dès que j'aurai un peu stabilisé l'ensemble. Je me doutais que ça existait, mais je n'avais pas vu la doc. Ayant appris sur le tas, j'ai pas mal de trous dans la raquette, mais ça se comble tout doucement

    Petite vérification : ton processeur a bien plusieurs coeurs ? Je parle de vrais coeurs, sans compter l'hyperthreading.
    Oui c'est un i7 4700MQ, donc 4 coeurs et 8 threads.

  6. #26
    Membre du Club
    Inscrit en
    Mars 2009
    Messages
    104
    Détails du profil
    Informations forums :
    Inscription : Mars 2009
    Messages : 104
    Points : 69
    Points
    69
    Par défaut
    Citation Envoyé par youtpout978 Voir le message
    Pourquoi utiliser à tout prix des datatables et non des listes typées ?
    Par simplicité principalement. J'ai hésité à utiliser une liste de tableaux de string mais ça suppose d'adresser chaque champ par son index plutôt que son nom, et j'en ai une trentaine.

    J'ai pas précisé ça pour pas compliquer la discussion, mais lors du traitement final de chaque lot (le gros de mon travail) je dois passer par une phase ou chaque ligne est éclatée en plusieurs lignes candidates parmi lesquelles j'arbitre ensuite. Dans mon cas, ça donne une liste de datatable (la datatable représentant un item). Ca donnerait une liste de liste de strings si j'utilisais une collection typée.

  7. #27
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    Citation Envoyé par billybobbonnet Voir le message
    Je n'écris rien sur le disque avant la fin du traitement d'un lot, hors du parallel.for. Chaque lot a sa propre datatable (en mémoire) qui lui est passée lors de son appel. il travaille dessus tout le long et ne produit une autre datatable qu'en fin de traitement, pour produire l'argument de la routine d'insert SQLite.
    Tu as séparé les lectures des écritures au sein d'un lot mais pas entre lots : pendant que tu écris le fruit du lot #1 tu opères peut-être des lectures depuis la DB pour le lot #2. Par ailleurs si ton niveau d'isolation est défini comme étant SERIALIZABLE et que tes lectures et écritures sont longues, elles ont beaucoup de chances d'entrer en conflit et de nécessiter des rollbacks (sqlite n'a pas recours au MVCC il me semble et son support de la concurrence est donc limité).

    En somme c'est ta phase préparatoire qui entrerait en conflit avec la phase d'insertion.

    Citation Envoyé par billybobbonnet Voir le message
    Par simplicité principalement. J'ai hésité à utiliser une liste de tableaux de string mais ça suppose d'adresser chaque champ par son index plutôt que son nom, et j'en ai une trentaine.
    Plutôt que des tableaux de chaînes, si ton analyse est si longue, ta DB petite par rapport à la mémoire et que chaque enregistrement est lu plusieurs fois, ne pourrais-tu pas envisager à la place de manipuler des POCO (plain old clr objects - des objets normaux, non-liés à une DB) ? Tu lirais directement un graphe d'objets et leurs champs. Le gain potentiel serait bien plus substantiel qu'une parallélisation : entre une lecture d'un champ de DataRow et celle d'un champ normal, la différence est environ de x20. Davantage dans certains cas.

  8. #28
    Membre du Club
    Inscrit en
    Mars 2009
    Messages
    104
    Détails du profil
    Informations forums :
    Inscription : Mars 2009
    Messages : 104
    Points : 69
    Points
    69
    Par défaut
    Citation Envoyé par DonQuiche Voir le message
    Tu as séparé les lectures des écritures au sein d'un lot mais pas entre lots : pendant que tu écris le fruit du lot #1 tu opères peut-être des lectures depuis la DB pour le lot #2. Par ailleurs si ton niveau d'isolation est défini comme étant SERIALIZABLE et que tes lectures et écritures sont longues, elles ont beaucoup de chances d'entrer en conflit et de nécessiter des rollbacks (sqlite n'a pas recours au MVCC il me semble et son support de la concurrence est donc limité).
    j'ai deux fichiers SQLite. En pratique, un des fichiers SQLite est uniquement lu lors du traitement d'un lot, l'autre est uniquement écrit lors de l'écriture du résultat d'un lot. En gros, un fichier n'est utilisé que pour des lectures, l'autre uniquement pour des écritures. Entre les deux, ce ne sont que des lectures écritures de datatable (le type) en mémoire. Pour rester clair, lorsque je disais:
    * je traite les items un par un et je remplis des cellules de la datatable du lot. C'est une écriture en mémoire, mais pas sur le disque
    Je n'ai pas précisé que je fais aussi une lecture dans le premier fichier SQLite à ce stade là. Donc je fais effectivement une lecture sur le disque. Pas de problème au niveau SQLite à priori vu que ce sont deux fichiers différents, mais sur les lectures écritures sur le disque en général c'est possible. J'ai 7ms en moyenne pour les lectures (sur une table d'environ 500k lignes). Je n'ai pas mesuré le temps des inserts par lot vu que l'ajout date d'aujourd'hui. Je verrai si ça pose problème lorsque j'aurai codé une version fonctionnelle du traitement des lots parallélisé.

    Citation Envoyé par DonQuiche Voir le message
    Plutôt que des tableaux de chaînes, si ton analyse est si longue, ta DB petite par rapport à la mémoire et que chaque enregistrement est lu plusieurs fois, ne pourrais-tu pas envisager à la place de manipuler des POCO (plain old clr objects - des objets normaux, non-liés à une DB) ? Tu lirais directement un graphe d'objets et leurs champs. Le gain potentiel serait bien plus substantiel qu'une parallélisation : entre une lecture d'un champ de DataRow et celle d'un champ normal, la différence est environ de x20. Davantage dans certains cas.
    Je n'imaginais pas que ce serait un gain de cet ordre. Je dois vraiment étudier cette alternative si le cas. Je ne connaissais pas ce type d'objets. A priori, si c'est un type, rien n'empêche, en plus, de paralléliser?

  9. #29
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    Citation Envoyé par billybobbonnet Voir le message
    j'ai deux fichiers SQLite.
    Ok, au temps pour moi.

    Je n'imaginais pas que ce serait un gain de cet ordre.
    En fait les données des DataRow sont stockées dans des dictionnaires au sein des DataColumn. Donc chaque fois que tu lis ou écris un champ tu te retrouves à lire ou écrire dans un dictionnaire, ce qui est de l'ordre de 20 ms. Une lecture dans un méthode virtuelle + vérifications + accès au tableau des paniers (buckets) + test + accès au tableau des enregistrements avec test de la clé + divers appels de méthodes.
    C'est le même principe qu'une hash table sauf que les clés sont ici des entiers (l'ID de la ligne).

    Je ne connaissais pas ce type d'objets.
    Ce sont des objets normaux en fait, le terme POCO est simplement utilisé pour les distinguer d'objets liés à la base de données et qui dissimulent toute une plomberie, comme DataRow / DataColumn.

    A priori, si c'est un type, rien n'empêche, en plus, de paralléliser?
    Tout à fait.

  10. #30
    Expert confirmé

    Homme Profil pro
    Développeur .NET
    Inscrit en
    Novembre 2010
    Messages
    2 065
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur .NET

    Informations forums :
    Inscription : Novembre 2010
    Messages : 2 065
    Points : 4 229
    Points
    4 229
    Par défaut
    Il faut qu'il utilise des DTO au lieu de POCO pour que ça soit le plus léger possible je pense.
    Il peut même les stocker dans une collection Hashset pour que ça soit le plus performant possible (s'il n'a pas d'objet qui doit être en double).

  11. #31
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    Citation Envoyé par youtpout978 Voir le message
    Il faut qu'il utilise des DTO au lieu de POCO pour que ça soit le plus léger possible je pense.
    Il peut même les stocker dans une collection Hashset pour que ça soit le plus performant possible (s'il n'a pas d'objet qui doit être en double).
    La distinction n'est pas pertinente ici : il n'est pas en train de concevoir un modèle à N couches, il cherche simplement à créer un graphe d'objets adapté à un traitement particulier.

    La décision de ce qui devra être inclus ou non dans ces classes ne relèvera pas d'une stricte adéquation à un motif de conception (pattern) mais de décisions ad hoc dans le contexte de son traitement. Il doit notamment rester libre de décider ou non d'inclure une logique métier dans ses classes selon que cela facilite ou non l'écriture de son code. Une telle logique métier n'est pas supposée entrer dans un DTO mais on ne s'attend pas non plus à voir figurer dans son code des procédures de validation comme ce serait d'ordinaire le cas dans un POCO. Comme je le disais on n'est pas dans le contexte d'une archi N-couches.

  12. #32
    Membre du Club
    Inscrit en
    Mars 2009
    Messages
    104
    Détails du profil
    Informations forums :
    Inscription : Mars 2009
    Messages : 104
    Points : 69
    Points
    69
    Par défaut
    Juste pour m'assurer que j'ai bien compris avant d'entamer des modifications lourdes de mon code. Substituer des objets poco à mes datatables reviendrait à écrire une classe avec autant de propriétés que j'ai de colonnes dans ma datatable? Par exemple, ça donnerait quelque chose comme:

    Code VB : 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
     
    Public Class Lot
        Private _Item() As Item
        Protected Friend Overridable Property Item() As Item()
            Get
                Return _Item() 
            End Get
            Set(ByVal value As Item())
                _Item = value
            End Set
        End Property
        Private _ItemNumber As Byte
        Protected Friend Overridable Property ItemNumber () As Byte
            Get
                Return _ItemNumber 
            End Get
            Set(ByVal value As Byte)
                _ItemNumber = value
            End Set
        End Property
    End Class
    Public Class Item
        Private _Content As String
        Protected Friend Overridable Property Content () As String
            Get
                Return _Content 
            End Get
            Set(ByVal value As String)
                _Content = value
            End Set
        End Property
        Private _ContentLenght As Integer
        Protected Friend Overridable Property ContentLenght() As Integer
            Get
                Return _Content.length
            End Get
               End Property
    End Class

    Quelques questions à propos du code ci-dessus:
    - Est-ce que c'est correct?
    - Est-ce que je peux, comme dans l'exemple (première propriété), utiliser un autre objet POCO comme type attendu d'une de mes propriétés?
    - Est-ce que je peux utiliser des propriétés intégrant un bout de méthode, comme ContentLenght, par exemple (la dernière)?
    - Est-ce qu'en dehors du get/set, il est possible/pertinent, d'ajouter des fonctions maison, comme par exemple un "AddItem" qui ajoute au tableau d'item un élément après un ReDim ou "LastItem" qui me sort le dernier item de la collection
    - Il n'y a pas de constructeur dans les exemples MSDN. Il n'est pas nécessaire?
    - Est-ce que l'accès simultané par plusieurs threads est possible en lecture (je suppose que oui) et en écriture (je suppose que non, en tout cas pas sans lock)
    - Dans mon exemple, je suis supposé accéder aux items en passant par l'index de la collection de lots. Sans avoir à créer une collection globale de tous les items qui regroupe toutes celles présentes dans les lots, est-ce qu'il est possible d'accéder aux items sans passer par l'index des lots mais en spécifiant directement leur index global?

    Si d'autres questions me viennent, je reviendrais

  13. #33
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    Citation Envoyé par billybobbonnet Voir le message
    - Est-ce que c'est correct?
    - Est-ce que je peux, comme dans l'exemple (première propriété), utiliser un autre objet POCO comme type attendu d'une de mes propriétés?
    - Est-ce que je peux utiliser des propriétés intégrant un bout de méthode, comme ContentLenght, par exemple (la dernière)?
    Oui, oui et oui. Encore que j'ai un doute sur ces parenthèses dans ta syntaxe VB. Par contre on peut faire plus simple avec les propriétés auto-implémentées :

    Code VB : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Friend Class Lot
        Protected Friend Overridable Property ItemNumber As Byte
        Protected Friend Overridable Property Item As Item

    Tu pourrais même utiliser de simples champs après tout, tu n'as pas forcément besoin de propriétés. Par ailleurs à toi de voir si tu as vraiment besoin de Overridable et s'il ne serait pas plus judicieux de déclarer le type en Friend et ses membres en Public.

    - Est-ce qu'en dehors du get/set, il est possible/pertinent, d'ajouter des fonctions maison, comme par exemple un "AddItem" qui ajoute au tableau d'item un élément après un ReDim ou "LastItem" qui me sort le dernier item de la collection
    Possible et sans doute pertinent. Fais ce qui te donnera le code le plus lisible, concis et évident. Il n'y a pas d'autre règles ici.

    - Il n'y a pas de constructeur dans les exemples MSDN. Il n'est pas nécessaire?
    Pas nécessaire : si tu n'en spécifies pas un constructeur par défaut est ajouté. Par ailleurs tu peux parfois avantageusement utiliser le mot-clé With plutôt que de créer des pelletées de constructeurs. Encore une fois écris ce qui te semble le plus lisible.

    Code VB : Sélectionner tout - Visualiser dans une fenêtre à part
    Dim theVar As New theClass With  {.theFirstProperty = "Sam",  .theSecondProperty = 12345}

    - Est-ce que l'accès simultané par plusieurs threads est possible en lecture (je suppose que oui) et en écriture (je suppose que non, en tout cas pas sans lock)
    Tu as raison : l'accès simultané est possible en lecture mais l'écriture requiert une synchronisation.

  14. #34
    Membre du Club
    Inscrit en
    Mars 2009
    Messages
    104
    Détails du profil
    Informations forums :
    Inscription : Mars 2009
    Messages : 104
    Points : 69
    Points
    69
    Par défaut
    Super! Merci pour ces réponses. Ça ouvre pas mal de perspectives, et ça risque de beaucoup me simplifier la tâche, en particulier avec l'ajout de fonctions spécifiques à certains champs. Le gain de performance est la cerise sur la gâteau.

    Je m'y mets de ce pas!

  15. #35
    Membre du Club
    Inscrit en
    Mars 2009
    Messages
    104
    Détails du profil
    Informations forums :
    Inscription : Mars 2009
    Messages : 104
    Points : 69
    Points
    69
    Par défaut
    J'ai un petit souci concernant l'organisation des données. Deux couches en cause:
    - J'ai une collection de types "Lot"
    - Chaque lot possède un champ qui stocke une collection de types "items".

    Le problème est dans la façon d'implémenter cette collection d'items, car au cours de mon algo je passe par trois étapes:
    - Je récupère une version primaire de chaque item dans la phase de préparation
    - J'éclate chaque item en plusieurs candidats, appartenant chacun à une famille de candidats. Là je passe à trois niveaux de profondeur, soit une liste de liste de liste d'items.(Niveau un, l'item, niveau deux, les familles de ses candidats, niveau 3 les candidats).
    -J'arbitre entre les différents candidats, et abouti à un candidat unique par item ou une short list de candidats. J'ai là 2 niveaux de profondeur maxi, l'item et ses candidats finaux (la famille étant une de ses propriétés).

    Dans ma démarche pour me débarrasser des datatables, j'ai stocké dans chaque objet de type "lot" les items. Pour maintenir la pertinence de cet objet tout le long du process, je vois plusieurs solutions mais je ne sais pas quelle est la meilleure ni si je vais dans la bonne direction:

    - Faire un type de "lot" différent pour chaque étape (le premier a comporte un champ "item()" le second un champ "item()()()" et le troisième un champ "item()()"
    - je commence d'emblée avec un champ "item()()()" quitte à devoir passer par trois indexes pour ajouter un seul objet
    - Je fais trois champs pour chaque étape dans l'objet "lot", qui correspondent à trois types d'objets différents. Par exemple: "rawitem()", "complexitem()()()" et "finalitem()()"
    - Faire une méthode "addItem" pour remplir le champ "item()()()" dans mon objet "lot" sans me soucier des indexs, mais ça n'ajoute qu'en simplicité et réduit performances (puisqu'on passe toujours par les trois index et qu'on doit en plus positionner l'item en fonction de ses propriétés)

    Edit:
    - Est-ce qu'il serait mieux/possible/plus rapide d'utiliser un dictionnaire avec en clé un tableau d'integer()()() et en valeur un type item,voire ou un tableau d'integer()() en clé et une collection d'item en valeurs

  16. #36
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    La réponse est un peu tardive mais enfin peut-être t'aidera t-elle tout de même...

    a) Pas d'optimisation prématurée ! C'est la meilleure façon d'obtenir un code sale, peu maintenable et peu performant (parce que ce que nous croyions rapide ne l'est pas toujours et parce qu'un code fastidieux est difficile à faire évoluer quand tu découvres, mesures à l'appui, une lourdeur insoupçonnée ou que tu veux tenter une approche différente). Évidemment si tu as le choix entre une solution en O(n²) et une en O(n), il est judicieux de choisir la seconde a priori mais si tu hésites simplement entre un tableau plutôt qu'une liste ou une méthode plutôt que deux, tu fais fausse route. Oublie les micro-optimisations et concentre-toi d'abord sur l'écriture d'un code propre. Tout à la fin tu pourras imaginer des micro-optimisations si c'est encore nécessaire. La première règle de l'optimisation est de ne pas optimiser.

    b) Les noms c'est le bien, c'est explicite. Si je vois une propriété SimilarItemsGroup dont le type est SimilarGroup, je comprends de quoi on cause. En revanche quand je vois une propriété Items de type Item()()(), je peste, comme tout le monde. Et il n'est pas dit que le second soit plus rapide (et il est encore moins probable que le second soit sensiblement plus rapide).

    c) Difficile d'en dire plus sans connaître le problème et la terminologie associée. Mais enfin évite les tableaux ou listes imbriqués. Si par exemple tu groupes par similarités, alors tu pourrais avoir :
    * Batch.SimilarityGroups du type List<SimilarityGroup>
    * Item.SimilarItems du type List<Items> et Lot.SimilarityRoots du type List<Item>
    * Item.NextSimilarItem du type Item et Lot.SimilarityRoots du type List<Item>
    * Etc.

    d) Si tu as vraiment besoin d'accéder à tes éléments par un triplet d'indices, alors u dictionnaire sera significativement plus lent qu'un tableau à moins que tes indices ne soient pas consécutifs ({1,2,3,4,5} -> tableau, {544564, 778946, 6465} -> dictionnaire). Quant à savoir si ce sera encore le cas entre trois tableaux imbriqués et un seul dictionnaire avec des triplets (via tuples ou autres), il faut tester. Cela dit je ne comprends pas pourquoi tu aurais besoin d'accéder à tes éléments par un triplet d'indices. ne peux-tu pas éliminer cette phase en ajoutant des membres à Item ?


    Note importante: je connais sans doute trop peu ton problème pour être vraiment pertinent.

  17. #37
    Membre du Club
    Inscrit en
    Mars 2009
    Messages
    104
    Détails du profil
    Informations forums :
    Inscription : Mars 2009
    Messages : 104
    Points : 69
    Points
    69
    Par défaut
    Merci encore de me suivre DonQuiche

    a) Tu as raison et c'est une tendance contre laquelle je lutte. Mais entre apprendre les bonnes pratiques et faire de la micro optimisation, la ligne est parfois mince.

    J'ai mis de côté cette question (je me suis stabilisé sur item()()() avant de lire ta réponse) quand j'ai levé un lièvre: comme vous me l'avez conseillé, j'ai minuté différentes parties de mon code et je me suis vite rendu compte qu'une de mes function SQLite, appelée environ 1/10 fois, utilisait un opérateur like qui évidemment n'utilisait pas l'index

    En parallèle, j'ai fini de me débarrasser des datatables et j'ai pu simplifier (sémantiquement) pas mal les parties les plus complexes du code. Une fois les fonctions SQLite refaites, voilà le résultat, côté performance:

    Avant: 00:00:22.2701806

    Après: 00:00:01.9584754

    Bon, le fichier de test est minuscule, mais c'est un excellent changement (même si je me sens un peu ridicule ). Le mieux dans tout ça, c'est que je n'ai pas encore ajouté la parallélisation des lots avec le threadpool vu que je travaille encore à l'embarquement dans un objet indépendant de toutes les fonctions nécessaires pour traiter un lot.

  18. #38
    Membre du Club
    Inscrit en
    Mars 2009
    Messages
    104
    Détails du profil
    Informations forums :
    Inscription : Mars 2009
    Messages : 104
    Points : 69
    Points
    69
    Par défaut
    Citation Envoyé par DonQuiche Voir le message
    Note importante: je connais sans doute trop peu ton problème pour être vraiment pertinent.
    Jusque là, je n'ai pas vraiment eu à me plaindre

  19. #39
    Expert confirmé

    Homme Profil pro
    Développeur .NET
    Inscrit en
    Novembre 2010
    Messages
    2 065
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur .NET

    Informations forums :
    Inscription : Novembre 2010
    Messages : 2 065
    Points : 4 229
    Points
    4 229
    Par défaut
    15 fois que je disais d'utiliser des liste typées , par contre Item()()() ça signifie quoi je ne suis pas familier avec le vb.net, quand j'écris mes classes si jamais j'ai un lot qui contient plusieurs item je crée simplement un objet Lot avec une propriété List<Item> après je ne sais pas si c'est ça que tu cherche à faire ne comprenant pas cette syntaxe.

  20. #40
    Membre du Club
    Inscrit en
    Mars 2009
    Messages
    104
    Détails du profil
    Informations forums :
    Inscription : Mars 2009
    Messages : 104
    Points : 69
    Points
    69
    Par défaut
    Citation Envoyé par youtpout978 Voir le message
    15 fois que je disais d'utiliser des liste typées , par contre Item()()() ça signifie quoi je ne suis pas familier avec le vb.net, quand j'écris mes classes si jamais j'ai un lot qui contient plusieurs item je crée simplement un objet Lot avec une propriété List<Item> après je ne sais pas si c'est ça que tu cherche à faire ne comprenant pas cette syntaxe.
    Et tu avais raison pour les listes typées.

    Pour item()()() c'est un tableau imbriqué, un tableau de tableau de tableau d'item. Les niveaux sont:

    1) le numéro de l'item
    2) les familles des candidats pour cet item
    3) les candidats pour chaque famille.

    Dans mon cas, l'objet 'lot' a la propriété (en l'occurrence le champ) item()()(). Ca marche bien même si je sais que ça n'est pas très lisible. Sauf à y gagner sensiblement en perf, je vais laisser comme ça même si ce n'est pas idéal. Je suis seul sur ce code et je préfère investir mon temps d'autres parties.

Discussions similaires

  1. Executer le traitement dans un thread ou BackgroundWorker
    Par skunkies dans le forum Windows Forms
    Réponses: 13
    Dernier message: 28/05/2009, 23h41
  2. Algorithme quand tu nous tiens : conditions logiques
    Par v4np13 dans le forum Algorithmes et structures de données
    Réponses: 9
    Dernier message: 21/12/2006, 19h31
  3. Probleme avec mon algorithme de tri
    Par kaygee dans le forum Langage
    Réponses: 6
    Dernier message: 09/01/2006, 21h23
  4. Problème lors de la transformation de mon "algorithm&qu
    Par prunodagen dans le forum Langage SQL
    Réponses: 8
    Dernier message: 27/04/2005, 21h48
  5. Débutant : architecture de mon site flash.
    Par Jazzy Troll dans le forum Flash
    Réponses: 3
    Dernier message: 12/01/2004, 16h36

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