2

我正在寻找一种方法来提高我使用构建的解析器的性能pyparsing。我阅读了 Packrat 解析,似乎这真的有助于我的解析器的性能。但是,当我启用 Packrat 解析时,性能变得更糟了!如果没有 packrat,解析一个 20 MB 的文件大约需要 2 分钟。启用 Packrat 后,它需要 2-3 倍的时间。我读到 packrat 可能会遇到 parseActions 的问题,所以我从语法中删除了所有 parseActions 以查看 packrat 是否会提高其性能,但这也无济于事。我尝试了不同的缓存大小限制(无限制,范围为 100-1000),但是当我启用 Packrat 时,所有这些方法反而恶化了我的解析器的性能。

是否有设置 Packrat 的 cache_size_limit 的经验法则?是否有任何语法结构限制了 Packrat 解析的使用或解释了为什么我的解析器的性能变差了?

4

1 回答 1

2

无论如何,一个 20MB 的文件都需要一些时间来进行 pyparsing,但是有些结构可能会减慢您的速度。

当我们重新实现 Packrat 解析以使用 OrderedDict 作为缓存时,我对缓存大小的不同值进行了一些测试。事实证明,默认值 128 的效率仅比无界缓存低 1-2%,但在内存和性能方面都取得了巨大的进步。因此,如果超过 200 或 300 的值对缓存有很大帮助,同时在缓存搜索和内存管理方面付出代价,我会感到惊讶。

当您通过在输入字符串的相同位置重新访问相同的表达式来获得缓存命中时,Packrat 解析效果最佳。重要的是它是完全相同的表达式,而不仅仅是一个等价的表达式。例如,如果你有类似的东西:

Literal("A") + Optional("," + Literal("B")) + Optional("," + Literal("C"))

分隔逗号的单独文字是不同的表达式,因此不会是缓存命中。相反,如果您为常用标点符号定义并重用单个表达式,则 Packrat 解析更有可能从缓存中查找其解析结果而不是重新解析:

COMMA = Literal(",")
Literal("A") + Optional(COMMA + Literal("B")) + Optional(COMMA + Literal("C"))

最近我一直在使用expr()语法来创建副本,expr以便解析操作、空白设置等修饰符不会在全局范围内意外更改。这将不利于表达式重用,因此如果您有很多expr()不需要的实例,那么只需重用 base expr。另请注意,带有结果名称的表达式会进行隐式复制,因此请确保不要过度使用结果名称。

Packrat 解析在使用 时是最好的infixNotation,尤其是当有 6 级或更多级别的运算符优先级时。生成重用子表达式的infixNotation表达式而不是定义新的子表达式,因此能够获得更好的缓存性能。

Or当您可以使用 '|' 时,您可能会过度使用 '^' 运算符 的运算符MatchFirst。当在递归表达式中使用时(使用Forward),这可能会特别昂贵。比较这两个表达式:

arith_operand = integer_expr ^ float_expr ^ variable_name ^ Keyword("pi") ^ Keyword("e")

通过使用'^',所有的表达式都会被计算,然后选择最长的匹配。特别是,如果解析“3.1416”,这是必要的,因为前导“3”会匹配integer,但较长的“3.1416”会匹配float_expr。但是我们也尝试匹配一个变量名,以及关键字“pi”和“e”,保证不匹配。(我们有与variable_name“pi”和“e”相同的表达式掩码问题,因为它们可能会匹配为可能的变量名。)但是如果我们改为使用“|”,那么我们的解析器将在第一个短路匹配。我们只需要注意解析的顺序:

arith_operand = float_expr | integer_expr | Keyword("pi") | Keyword("e") | variable_name

也就是说,我们必须确保在匹配整数之前检查浮点数是否匹配,因为浮点数的前导部分可能会被误认为是整数。

如果您有容易相互排斥的替代方案(例如一系列可能的关键字,由于它们的关键字性质,它们永远不会相互掩盖),那么您还可以根据一些可能的频率对它们进行排序。例如:

bad_expr = Keyword("xylophone") | Keyword("the") | Keyword("a")

在大多数非音乐应用程序中可能会成为性能损失者。

在 pyparsing 的早期,我没有这个Regex类,所以我必须使用类似Combine(Word(nums) + "." + Optional(Word(nums))). 当您可以使用Regex(r"\d+\.\d*"). 通常,实数表达式是在给定的解析运行中使用和测试数千次的真正低级表达式,因此转换为Regex真正的回报。或使用pyparsing_commonlikepyparsing_common.real或中的数字表达式之一pyparsing_common.number。不过,您不需要太过分 - pyparsing 在内部使用正则表达式来匹配使用Wordand的表达式oneOf

您还可以直接检查缓存和缓存统计信息,ParserElement.packrat_cache并且ParserElement.packrat_cache_stats. 缓存统计信息是一个包含两个元素的列表,元素 0 是缓存命中数,元素 1 是未命中数。您可以为将打印出缓存统计信息的重复表达式定义调试操作。您还可以使用 Counter: 查找重复的表达式Counter(str(key[0]) for key in ParserElement.packrat_cache)。通过str-ing 表达式,它将有助于识别重复项。因此,您可以查看不同缓存大小值的缓存效率。

编辑:我的错误,对缓存键的迭代ParserElement.packrat_cache不起作用,缓存OrderedDict本身隐藏在外部访问之外。

于 2018-07-19T01:41:57.983 回答