【问题标题】:Why does FParsec use lists?为什么 FParsec 使用列表?
【发布时间】:2015-04-01 21:02:12
【问题描述】:

我想我会尝试使用 FParsec 编写一个快速解析器,并很快意识到many 返回一个列表是一个严重的性能问题。然后我在文档中发现了一个使用ResizeArray 的替代方法:

let manyA2 p1 p =
    Inline.Many(firstElementParser = p1,
                elementParser = p,
                stateFromFirstElement = (fun x0 ->
                                             let ra = ResizeArray<_>()
                                             ra.Add(x0)
                                             ra),
                foldState = (fun ra x -> ra.Add(x); ra),
                resultFromState = (fun ra -> ra.ToArray()),
                resultForEmptySequence = (fun () -> [||]))

let manyA p = manyA2 p p

在我的代码中使用它会使其运行速度提高几倍。那么为什么 FParsec 默认使用列表而不是 ResizeArray

【问题讨论】:

  • 除非团队成员做出贡献,否则这充其量只是猜测......因此可能不适合stackoverflow。从编写类似内容的个人经验来看,专注于让事情以可维护的方式运行而不是性能往往是一种更有利可图的策略……至少对于产品的前几个版本而言。只要某件事“足够快”地工作,那么每个人都很高兴。
  • 我同意,但作者已回复。 :-)

标签: fparsec


【解决方案1】:

使用内置的 F# 列表类型作为序列组合器的结果类型使得组合器在 F# 中使用起来更方便,并且可以说会导致更惯用的客户端代码。由于大多数 F# 开发人员更看重简单和优雅而不是性能(至少在我的经验中),因此在我设计 API 时,使用列表作为默认值似乎是正确的选择。同时,我尝试让用户轻松定义自己的专用序列组合器。

目前,返回列表的序列组合器也在内部使用列表来构建序列。这对于具有超过 2 个元素的序列来说是次优的,因为列表必须在返回之前反转。但是,我不确定更改实现是否值得付出努力,因为如果您的解析器对性能敏感并且您正在解析长序列,那么您最好不要使用列表。

我可能应该在用户指南的性能章节中添加一个关于使用数组而不是列表的部分。

【讨论】:

  • 有道理,谢谢。现在我想起来了,我不确定有什么更好的选择。 ResizeArraymany 中会很棒,但如果你有 many (many ...),那么你会想要一些可以有效连接的东西。
  • 对于性能关键的解析器(组合器)功能,您始终可以选择下拉到 FParsec 的低级 API 并“手动”实现解析器。对于嵌套序列组合器,这将是例如允许您将元素解析到单个容器中。它还允许您使用直接的 CharStream 方法调用跳过分隔符和空格,这可能会给您带来一点额外的性能提升。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-12
  • 2016-11-20
相关资源
最近更新 更多