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

Python Discussion :

Optimisation : liste de regex dynamique [Python 3.X]


Sujet :

Python

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Mai 2010
    Messages
    72
    Détails du profil
    Informations personnelles :
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations forums :
    Inscription : Mai 2010
    Messages : 72
    Par défaut Optimisation : liste de regex dynamique
    Bonjour,

    Nous avons des besoins d'analyse de fichiers (texte, des logs en fait) évolutif.
    Suivant les besoins, il nous faut donc y chercher tel ou tel valeur à tel ou tel emplacement.
    J'ai fait rapidement (... contrainte immédiate ^^) un petit script python qui va analyser les fichiers présents (je zappe les spécifications sur ce point, pas d'importance ici) et récupérer 3 ou 4 groupes (enregistrés dans un fichier texte ou une bdd).

    Afin qu'il soit facilement utilisable et adaptable, la regex est configuré via un fichier .ini (par lot/type/source de fichiers).

    Le script va donc récupérer la liste de fichier suivant le paramétrage et pour chaque va vérifier le pattern souhaité ligne par ligne en récupérant le cas échéant les données. Jusque là, tout va bien.
    Seulement, nous pouvons avoir plusieurs recherches nécessaire. Avec des milliers de fichiers (voir plus!), faire plusieurs passes est hors de question!
    Le fichier de configuration permet donc d'indiquer plusieurs regex (avec un nombre de groupe de capture variable suivant le besoin).

    Côté script, pour chaque ligne je compile le Regex de chaque groupe de configuration présent et traire avec celui-ci.
    Sauf que...
    J'ai testé avec une regex, puis deux, et le temps de traitement double! Oui, j'ai deux regex, mais le parcours des fichiers hors traitement regex me semblait plus conséquent dans le temps global de traitement.
    D'ailleurs, une première version non dymanique avait les deux regex dans le script. Et pour chaque ligne, je traitais celles-ci. Seules différences : elles étaient compilées hors boucle de lecture (et inscrite dans le python au lieu du fichier de conf). Côté résultat... moitié moins de temps (peu de différence qu'on ai une ou deux regex en fait).

    Bref :
    - pourquoi est-ce si lourd? L'opération "re.compile" est-elle si lourde en temps (je ne vois que ça comme explication)?
    - auriez-vous des suggestions d'amélioration? Concaténer (|) les regex? Les compiler hors boucle? A votre avis, quelle est l'option la plus efficace? Sachant qu'il faudra que je gère le titre de la recherche (un par regex).

    Le code actuel :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    for file in list_files:  
        for line in open(file):  
            for groupe in config.sections():
                pattern = config[groupe]['Regex']
                pattern = re.compile(pattern)
                titre = config[groupe]['Nom']
                for match in re.finditer(pattern, line):
                    with open(logfile, 'a') as f:
                        f.write('{};{};{};{}\n'.format(titre,match.group('Source'),match.group('Date'),match.group('Valeur'),match.group('Valeur2')))
                    f.closed
    Exemple de regex :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    [GROUP1]
    Regex=(?P<Source>\d{5})\s*\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}.*NumSerie\s(?P<Valeur>\d{5})(?P<Date>)
    Nom=SN
    [GROUP2]
    Regex=(?P<Source>\d{5})\s*(?P<Date>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}).*Reboot.* Nouvelle valeur =(?P<Valeur>ERR|INC|NOK) 
    Nom=Erreur démarrage
    Je ne suis pas encore bloqué et vais continuer mes tests.
    Mais si vous avez des conseils avisés, ça m'évitera de passer du temps sur un mauvais choix!
    Merci!


    PS : Bon, en relisant au calme, je devrais réfléchir à l'intérêt du compile si je le relance à chaque boucle...
    Du coup, je penses partir sur une liste créé au départ selon la configuration, et parcourue à chaque analyse de ligne. Si ce n'est pas la meilleure solution, merci pour vos retours sinon je poursuis dans cette voie.

  2. #2
    Expert confirmé

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    4 307
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2008
    Messages : 4 307
    Par défaut
    Salut,

    Et si tu compiles une seule fois avant la boucle, ça change les performances ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    patterns = []
    for groupe in config.sections():
        patterns.append(re.compile(config[groupe]['Regex']))
    for file in list_files:  
        for line in open(file):
            for pat in patterns:
            ....

  3. #3
    Membre confirmé
    Profil pro
    Inscrit en
    Mai 2010
    Messages
    72
    Détails du profil
    Informations personnelles :
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations forums :
    Inscription : Mai 2010
    Messages : 72
    Par défaut
    N'étant pas un expert et de loin, je craignais être en train de faire un truc lourd et inefficient au possible en passant à côté de la bonne pratique / bonne méthode pour ce genre de traitement ;-).

    Sinon, j'ai fait tourner :
    - avec le code ci-dessous, compile des regex à chaque ligne (et regex)
    - en supprimant le compile (qui si je ne me trompes sert uniquement à stocker dans un objet pour réutiliser... le recompiler à chaque ligne, pas d'intérêt je supposes.
    - avec ta solution VinsS, je compile tout avant

    Compile par ligne = 76s (bouh!)
    Sans compile = 65s (mieux, mais encore pas top)
    Compile avant la boucle = 35s...
    Du coup, je crois que le choix est tout fait! Il me reste à intégrer les titres (à sortir et différent suivant le pattern) mais au moins j'ai l'axe à suivre, merci bien!

    Pour mon propre apprentissage et d'après ce constat, compiler une regex permet donc de gagner en temps de traitement à chaque appel?

    Autre question : y a-t-il un "format" autre qu'une liste pour chercher selon plusieurs regex possibles (avec groupes)?
    C'était une solution que j'avais en tête.
    J'ai testé :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    (?P<Date>\d{4}-\d{2}-\d{2})|(?P<Date>\d{2}-\d{2}-\d{4})
    ou 
    (?:(?P<Date>\d{4}-\d{2}-\d{2}))|(?:(?P<Date>\d{2}-\d{2}-\d{4}))
    qui, je l'espérait, me récupérerait la capture "date" dans les formats 25-01-2016 ou 2016-01-06, mais ce n'est pas le cas. Apparemment, avoir un groupe nommé pareil en deux endroits, même si c'est dans un "OU", ça ne passe pas.
    (bien sûr, ce n'est qu'un exemple je pourrait faire (?P<Date>(?:\d{4}-\d{2}-\d{2})|(?:\d{2}-\d{2}-\d{4})) ici mais avec des regex complète comme mon post initial les éléments variants ne sont pas dans les groupes nommés eux-mêmes)

  4. #4
    Expert confirmé Avatar de BufferBob
    Profil pro
    responsable R&D vidage de truites
    Inscrit en
    Novembre 2010
    Messages
    3 041
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : responsable R&D vidage de truites

    Informations forums :
    Inscription : Novembre 2010
    Messages : 3 041
    Par défaut
    salut,

    Citation Envoyé par Shampra Voir le message
    Pour mon propre apprentissage et d'après ce constat, compiler une regex permet donc de gagner en temps de traitement à chaque appel?
    oui, c'est même à ça que sert la fonction en fait
    "using re.compile() and saving the resulting regular expression object for reuse is more efficient when the expression will be used several times in a single program."

    je pourrait faire (?P<Date>(?:\d{4}-\d{2}-\d{2})|(?:\d{2}-\d{2}-\d{4})) ici mais avec des regex complète comme mon post initial les éléments variants ne sont pas dans les groupes nommés eux-mêmes)
    je ne vois pas ce que tu veux dire ici, si tu n'as que le champ Date la regex remplit sa fonction, les regex que tu donnes dans ton premier post ne semblent pas plus compliquées (ou alors j'ai loupé un truc)

    quant au fait d'ouvrir/fermer le fichier à chaque itération ça génére des i/o, par nature lourdes et plombantes en terme de perfs
    on pourra faire aussi attention à l'utilisation du greedy operator .* qui peut souvent être remplacé par un opérateur non-gourmand .*?, selon la distance entre les deux champs à matcher de chaque côté du .* et le nombre de fois qu'on répète la regex, ça peut possiblement économiser quelques cycles

  5. #5
    Expert confirmé Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 986
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Justicier interdimensionnel

    Informations forums :
    Inscription : Mars 2009
    Messages : 2 986
    Par défaut
    Normalement un fichier log est (devrait être) quelque chose de plutôt calibré, et l'utilisation de regex pour le parser devrait être minimaliste, voire inutile. C'est pour cela qu'il serait bon que tu postes un ou plusieurs extraits significatifs de tes logs pour que l'on puisse voir le bien fondé de l'utilisation des regex.

  6. #6
    Membre confirmé
    Profil pro
    Inscrit en
    Mai 2010
    Messages
    72
    Détails du profil
    Informations personnelles :
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations forums :
    Inscription : Mai 2010
    Messages : 72
    Par défaut
    Ok pour l'ouverture du fichier, je ne l'ouvre plus qu'une seule fois .
    Pour le "greedy operator", merci du conseil, je vais regarder ça (même si dans mon cas le gain sera trop minime, toujours bien de connaitre les bonnes pratiques).

    @CosmoKnacki Malheureusement non. Ou plus un type de "log" est calibré, j'en ai environ 40 (plusieurs systèmes différents, qui envois des fichiers de plusieurs types différents; certains sont bien calibrés comme ceux que je traite avec ces regex, d'autres beaucoup moins, d'autres sont en xml, etc...). Comme suivant la situation on peut avoir besoin de récupérer des informations dans l'un ou l'autre de ces fichiers, j'ai opté pour une regex mis en fichier de configuration, ça marche pour quasiment tous.
    Par contre, parser sans regex? Par curiosité, peux tu m'indiquer comment (ou pointer vers un article sur ça... j'ai regardé via google, les parsers regardés utilisent des regex).

    @BufferBob Comme dit, la date n'est là que pour illustrer plus succinctement.
    Pour reprendre la question avec un exemple réel...
    J'ai des fichiers textes (très bien calibré ceux-là), contenant entre autres lignes :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    ...
    00001	2016-01-19 08:39:25	D	ACTIVITE	Configuration Service - Stat =80% Lancement =Auto NumSerie = 12345
    ...
    00001	2016-01-19 08:39:25	D	Reboot	Modification du service - Ancienne Valeur =NOK Nouvelle valeur =ERR	
    ...
    00001	2016-01-19 08:39:25	D	Reboot	Modification du service - Ancienne Valeur =ERR Nouvelle valeur =NOK
    ...
    Ceux-là sont bien calibrés, d'autres n'ont pas de date ou pas de formalisme du tout...

    Suite à une demande, je dois les traiter pour récupérer les valeurs et les enregistrer dans un même endroit.
    Selon les fichiers ou les besoins, les valeurs à récupérer peuvent différer ou avoir un ordre différent dans le texte
    Par exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    0001 Erreur critique sur le module 12345
    Test reboot - Module 12345
    Solution que j'ai choisie : nommer les groupes à récupérer, avec au moins DATE, SOURCE, VALEUR
    Si pas de groupe date, on met un groupe vide en fin de regex. Si plus de 3 groupes à récupérer, le suivant sera par id (et devra être en fin de chaine, mais ça conviendra).

    Regex faites :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    Regex=(?P<Source>\d{5})\s*\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}.*NumSerie\s(?P<Valeur>\d{5})(?P<Date>)
    Nom=SN
    [GROUP2]
    Regex=(?P<Source>\d{5})\s*(?P<Date>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}).*Reboot.* Nouvelle valeur =(?P<Valeur>ERR|INC|NOK)
    ...
    Voilà l'exemple réel ;-).
    Donc la question : y a-t-il une méthode pour combiner (avec un "OU") ces deux regex, en gardant les champs nommés? Juste pour éviter de traiter chaque lignes une fois pour chaque regex.
    Comme dit, j'ai tenté :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    (?:(?P<Source>\d{5})\s*\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}.*NumSerie\s(?P<Valeur>\d{5})(?P<Date>))|(?:(?P<Source>\d{5})\s*(?P<Date>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}).*Reboot.* Nouvelle valeur =(?P<Valeur>ERR|INC|NOK))
    ... qui est refusé car j'ai deux groupes de capture avec le même nom.

    Et non : faire une seule regex commune est difficilement envisageable. J'en ai déjà 4 actives, trouver la regex qui réponde à tout va vite devenir une activité à part entière surtout sur des fichiers moins bien formaté!

  7. #7
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 738
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 738
    Par défaut
    Salut,

    Citation Envoyé par Shampra Voir le message
    Bref :
    - pourquoi est-ce si lourd? L'opération "re.compile" est-elle si lourde en temps (je ne vois que ça comme explication)?
    - auriez-vous des suggestions d'amélioration? Concaténer (|) les regex? Les compiler hors boucle? A votre avis, quelle est l'option la plus efficace? Sachant qu'il faudra que je gère le titre de la recherche (un par regex).
    Un pouième de seconde multiplié par 1000 ou 10000 çà fait des secondes et/ou des minutes.
    Hormi la remarque de VinsS, rien que le "with open(logfile, 'a') as f" à chaque itération coûte sans rien apporter.

    Après il faut voir à quoi ressemblent vos lignes mais "re.finditer(pattern, line)" suppose que vous allez trouver le pattern plusieurs fois... alors qu'une ligne ressemblera plutôt soit au premier soit au second. De plus les 2 patterns semblent commencer par la même séquence: pourquoi refaire le boulot?

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  8. #8
    Membre confirmé
    Profil pro
    Inscrit en
    Mai 2010
    Messages
    72
    Détails du profil
    Informations personnelles :
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations forums :
    Inscription : Mai 2010
    Messages : 72
    Par défaut
    Le openfile à chaque itération était pour éviter de le laisser ouvert en cas de problème (ex : quand un fichier illisible se glisse dans la liste, j'ai eu le cas).
    Est-ce réellement utile, à voir effectivement. Je l'ai fait pour faire "au mieux". Côté consommation, en ne l'ouvrant qu'une fois je gagne 5s sur mon lot de référence (15% quand même).

    Pour le pattern, il ne démarre pas toujours pareil, je ne peux cumuler les deux regex (ou n'est pas trouvé comment, la chaine complète pouvant être différente et devant contenir plusieurs groupes de capture, voir réponse plus haut).
    De même une ligne pourrait répondre à plusieurs pattern (mais ça, je peux le régler en jouant sur les groupes de captures pour limiter les regex tout en répondant aux demandes qui me sont faites). Je note au cas où...
    Par contre un seul résultat par pattern puisque traitement par ligne, re.search serait préférable peut-être? Testé à l'instant, pas de différence de temps de traitement

    D'ailleurs : est-ce préférable (point de vue qualité du code, efficience,...) de faire un finditer sur un fichier complet ou un search par ligne?

    Merci pour les remarques!
    Je pose beaucoup de questions pour bien comprendre tout ce qui entoure la question initiale (résolue sinon, je vais la passer) et apprécie vos réponses!

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

Discussions similaires

  1. [C#] Comment créer une liste d'image dynamiquement ?
    Par gwenhael dans le forum ASP.NET
    Réponses: 3
    Dernier message: 08/09/2006, 11h30
  2. Réponses: 5
    Dernier message: 10/07/2006, 15h02
  3. [Avancé][Optimisation] Charger des librairies dynamiquement
    Par Wookai dans le forum Général Java
    Réponses: 12
    Dernier message: 12/08/2005, 16h34
  4. Réponses: 10
    Dernier message: 04/05/2004, 16h00

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