【问题标题】:PLY: quickly parsing long lists of items?PLY:快速解析一长串项目?
【发布时间】:2011-06-20 20:01:37
【问题描述】:

我在PLY 中使用一个相当简单的解析器,我的一条规则采用以下形式:

def p_things(p):
    '''
    things : thing things
    things : thing
    '''
    p[0] = [p[1]]
    if len(p) == 3:
        p[0] += p[2]

输入文件通常是things 的简单列表,因此解析本身并不复杂。然而,我的一些输入文件非常大(经常超过 100,000 行,在极端情况下超过 1,000,000)。在分析(通过cProfile and pstats)中,大部分运行时都被重复调用p_things 占用——大概是对things 列表中的每个项目的调用。

有没有办法减少这个时间,或更有效的方式来构建这个规则?到目前为止,我看到的大多数答案(以及我发现的规范编译器信息)都将此方法列为构造可解析项列表的公认方法,无论长度如何。

【问题讨论】:

    标签: python parsing optimization ply


    【解决方案1】:

    原来我忘记了一些基本的编译器理论。 PLY 是一个 LALR(1) 解析器,因此最好将规则写成:

    def p_things(p):
        '''
        things : things thing
        things : thing
        '''
        if len(p) == 2:
            p[0] = [p[1]]
        else:
            p[0] = p[1]
            p[0].append(p[2])
    

    虽然它可能看起来更冗长,但实际上有一个显着的改进 - 在 PLY 或 Python 的某个地方,解析器能够对左递归形式应用一些优化。我已经看到较大的输入文件的性能从指数下降到线性;一个样本,在things 列表中包含超过一百万个项目,运行时间不到 20%。

    【讨论】:

    • 这本身不是解析问题——在您的原始方法中,p[0] += p[2] 每次添加新的thing 时都会线性运行到列表的长度,因此解析列表需要二次时间,而在您的新方法中,添加新元素 p[0].append(p[2]) 会运行摊销的常数时间,因此总体而言,解析列表需要线性时间。
    • Secondary:将你的规则一分为二,比如一个以_iter为后缀,另一个以_end为后缀。正如 PLY 的文档已经推荐的那样,选择语句只是复制解析器本身已经完成的工作,以区分生产规则中的两种情况。这将节省一些计算,但与观察到效率的提高无关。
    • 如果您有一个数据文件,其中包含需要 PLY 的复杂标题,以及非常简单数据的巨大尾部,那么请考虑将输入分成两部分。将第一个(小)传递给 PLY,并在每行的简单迭代中处理第二个,按词法拆分每行(例如,如果它是 3 个坐标的元组)。
    【解决方案2】:

    在不更改代码的情况下,您可以尝试使用“PyPy”版本的 Python 进行即时编译——它运行代码的速度可能比普通 CPython 快。

    【讨论】:

    • 如果“大部分时间”都花在那个函数上,那可能更多的是一个复杂性问题,no 语言实现可以解决(very 智能分析器查看非常个简单案例)。
    • 好建议,但不幸的是,我遇到了 PyPy 的一些兼容性问题(在这种情况下,PLY 嵌入到了一些其他包中,可能比它们应该更喜欢 CPython)。此外,@delnan 可能有一点 - 这让我觉得这更像是一个运行时复杂性问题而不是编译问题。
    【解决方案3】:

    总结一下:

    • 性能问题与解析器本身无关(而是+= 的使用)
    • 通过使 RHS 明确,使 LALR(1) 解析器的工作变得更容易。
    • 如果可能的话,最好通过拆分规则来避免不必要的选择语句 (ifs)。

    为了更好地理解Ioannis Filippidis' comment,实际可视化它更容易。这就是我认为的意思,也是我最终的大致意思。

    def p_things_iter(p):
        '''
        things_iter : things thing
        '''
            p[0] = p[1]
            p[0].append(p[2])
    
    def p_things_end(p):
        '''
        things_iter : thing
        '''
        p[0] = [p[1]]
    
    def p_things(p):
        '''
        things : things_iter
        things : things_end
        '''
        p[0] = p[1]
    

    【讨论】:

      猜你喜欢
      • 2023-02-22
      • 2022-08-22
      • 2020-03-26
      • 2020-02-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-03
      • 2014-10-14
      相关资源
      最近更新 更多