2016-07-29 1 views
1

je un assez grand délimité qui se compose de blocs délimités parAmélioration de la vitesse d'analyse syntaxique du fichier de configuration configuration bloc

#start <some-name> ... #end <some-name> étaient some-name doit être le même pour le bloc. Le bloc peut apparaître plusieurs fois mais n'est jamais contenu en lui-même. Seuls quelques autres blocs peuvent apparaître dans certains blocs. Je ne suis pas intéressé par ces blocs contenus, mais sur les blocs du deuxième niveau.

Dans le fichier réel, les noms ne commencent pas par blockX mais sont très différents les uns des autres.

Un exemple:

#start block1 

    #start block2 

    /* string but no more name2 or name1 in here */ 
    #end block2 

    #start block3 
    /* configuration data */ 
    #end block3 

#end block1 

Ceci est en cours d'analyse avec regex et, lorsqu'il est exécuté sans débogueur attaché, assez rapide. 0.23s pour un fichier 2.7MB 2k avec des règles simples comme:

blocks2 = re.findAll('#start block2\s+(.*?)#end block2', contents) 

I Tried avec cette analyse syntaxique pyparsing mais la vitesse est très lent, même sans un débogueur attaché, il a fallu 16s pour le même fichier. Mon approche consistait à produire un code de pypars qui imiterait l'analyse simple de l'expression régulière afin que je puisse utiliser une partie de l'autre code pour l'instant et éviter d'avoir à analyser chaque bloc maintenant. La grammaire est assez étendue.

Voici ce que j'ai essayé

block = [Group(Keyword(x) + SkipTo(Keyword('#end') + Keyword(x)) + Keyword('#end') - x)(x + '*') for x in ['block3', 'block4', 'block5', 'block6', 'block7', 'block8']] 

blocks = Keyword('#start') + block 

x = OneOrMore(blocks).searchString(contents) # I also tried parseString() but the results were similar. 

Qu'est-ce que je fais mal? Comment puis-je optimiser cela pour arriver à peu près à la vitesse atteinte par l'implémentation de regex?

Edit: L'exemple précédent était moyen de facile par rapport aux données réelles, donc je créé un bon moment:

/* all comments are C comments */ 
VERSION 1 0 
#start PROJECT project_name "what is it about" 
    /* why not another comment here too! */ 
    #start SECTION where_the_wild_things_are "explain this section" 


     /* I need all sections at this level */ 

     /* In the real data there are about 10k of such blocks. 
      There are around 10 different names (types) of blocks */ 


     #start INTERFACE_SPEC 
     There can be anything in the section. Not Really but i want to skip anything until the matching (hash)end. 
     /* can also have comments */ 

     #end INTERFACE_SPEC 

     #start some_other_section 
      name 'section name' 

      #start with_inner_section 
       number_of_points 3 /* can have comments anywhere */ 
      #end with_inner_section 
     #end some_other_section /* basically comments can be anywhere */ 

     #start some_other_section 
      name 'section name' 
      other_section_attribute X 
      ref_to_section another_section 
     #end some_other_section 

     #start another_section 
      degrees 
      #start section_i_do_not_care_about_at_the_moment 
       ref_to some_other_section 
       /* of course can have comments */ 
      #end section_i_do_not_care_about_at_the_moment 
     #end another_section 

    #end SECTION 
#end PROJECT 

Pour cela, je devais élargir votre proposition initiale. J'ai codé dur les deux blocs externes (PROJECT et SECTION) car ils DOIVENT exister.

Avec cette version, le temps est encore à ~ 16s:

def test_parse(f): 
     import pyparsing as pp 
     import io 
     comment = pp.cStyleComment 

     start = pp.Literal("#start") 
     end = pp.Literal("#end") 
     ident = pp.Word(pp.alphas + "_", pp.printables) 

     inner_ident = ident.copy() 
     inner_start = start + inner_ident 
     inner_end = end + pp.matchPreviousLiteral(inner_ident) 
     inner_block = pp.Group(inner_start + pp.SkipTo(inner_end) + inner_end) 

     version = pp.Literal('VERSION') - pp.Word(pp.nums)('major_version') - pp.Word(pp.nums)('minor_version') 

     project = pp.Keyword('#start') - pp.Keyword('PROJECT') - pp.Word(pp.alphas + "_", pp.printables)(
       'project_name') - pp.dblQuotedString + pp.ZeroOrMore(comment) - \ 
       pp.Keyword('#start') - pp.Keyword('SECTION') - pp.Word(pp.alphas, pp.printables)(
       'section_name') - pp.dblQuotedString + pp.ZeroOrMore(comment) - \ 
       pp.OneOrMore(inner_block) + \ 
       pp.Keyword('#end') - pp.Keyword('SECTION') + \ 
       pp.ZeroOrMore(comment) - pp.Keyword('#end') - pp.Keyword('PROJECT') 

     grammar = pp.ZeroOrMore(comment) - version.ignore(comment) - project.ignore(comment) 

     with io.open(f) as ff: 
       return grammar.parseString(ff.read()) 

EDIT: Typo, dit qu'il était 2k mais il est plutôt un fichier 2.7MB.

Répondre

1

tout d'abord, ce code ne fonctionne comme affiché pas pour moi:

blocks = Keyword('#start') + block 

Changer à ceci:

blocks = Keyword('#start') + MatchFirst(block) 

au moins va à l'encontre de votre échantillon de texte.

Plutôt que de coder en dur tous les mots-clés, vous pouvez essayer d'utiliser l'une des expressions d'adaptation de pyparsing, matchPreviousLiteral:

(ÉDITÉ)

def grammar(): 
    import pyparsing as pp 
    comment = pp.cStyleComment 

    start = pp.Keyword("#start") 
    end = pp.Keyword('#end') 
    ident = pp.Word(pp.alphas + "_", pp.printables) 
    integer = pp.Word(pp.nums) 

    inner_ident = ident.copy() 
    inner_start = start + inner_ident 
    inner_end = end + pp.matchPreviousLiteral(inner_ident) 
    inner_block = pp.Group(inner_start + pp.SkipTo(inner_end) + inner_end) 

    VERSION, PROJECT, SECTION = map(pp.Keyword, "VERSION PROJECT SECTION".split()) 

    version = VERSION - pp.Group(integer('major_version') + integer('minor_version')) 

    project = (start - PROJECT + ident('project_name') + pp.dblQuotedString 
       + start + SECTION + ident('section_name') + pp.dblQuotedString 
       + pp.OneOrMore(inner_block)('blocks') 
       + end + SECTION 
       + end + PROJECT) 

    grammar = version + project 
    grammar.ignore(comment) 

    return grammar 

Il est seulement nécessaire d'appeler ignore() sur la expression la plus haute dans votre grammaire - elle se propagera à toutes les expressions internes. En outre, il ne devrait pas être nécessaire de saupoudrer ZeroOrMore(comment) s dans votre grammaire, si vous avez déjà appelé ignore().

J'ai analysé une chaîne d'entrée de 2 Mo (contenant 10 000 blocs internes) en environ 16 secondes, de sorte qu'un fichier 2K ne devrait prendre que 1/1000ème de long.

+0

Merci pour le conseil sur 'matchPreviousLiteral'. Il n'est pas documenté dans http://pyparsing.wikispaces.com/HowToUsePyparsing comme le sont certaines autres fonctions. Y a-t-il une documentation plus à jour? – RedX

+0

Je garde les docs en ligne à jour avec chaque version, à https://pythonhosted.org/pyparsing/. La page de John Shipman à http://infohost.nmt.edu/tcc/help/pubs/pyparsing/web/index.html est également utile, bien qu'un peu opiniâtre. Voir mes modifications ci-dessus pour quelques recommandations de nettoyage sur votre grammaire. – PaulMcG

+0

La taille de fichier que j'ai indiquée au début était de 1000. Elle était de 2,7 Mo au départ et non de 2 Ko. Désolé pour ça. Je suppose que je ne serai pas en mesure d'utiliser pyparsing dans ce projet alors même si le résultat serait plus utile, mais la différence de temps est trop. – RedX