rwa*_*ace 3 f# parsing closures
我正在用F#编写一个解析器,它需要尽可能快(我希望在不到一分钟的时间内解析一个100 MB的文件).正常情况下,它使用可变变量来存储下一个可用字符和下一个可用标记(即词法分析器和解析器正确使用一个前瞻单元).
我当前的部分实现使用局部变量.因为闭包变量不可变(任何人都知道这个的原因吗?)我已经将它们声明为ref:
let rec read file includepath =
let c = ref ' '
let k = ref NONE
let sb = new StringBuilder()
use stream = File.OpenText file
let readc() =
c := stream.Read() |> char
// etc
Run Code Online (Sandbox Code Playgroud)
我认为这有一些开销(不多,我知道,但我在这里尝试最大速度),而且它有点不优雅.最明显的替代方法是创建一个解析器类对象,并将可变变量作为其中的字段.有谁知道哪个可能更快?是否有任何共识被认为是更好/更惯用的风格?我还缺少另一种选择吗?
您提到闭包不能捕获本地可变值,因此您需要使用ref.原因是需要在堆上分配在闭包中捕获的可变值(因为闭包是在堆上分配的).
F#强制您明确地写(使用ref).在C#中,您可以"捕获可变变量",但编译器会将其转换为场景后面的堆分配对象中的字段,因此无论如何它都将在堆上.
总结是:如果要使用闭包,需要在堆上分配可变变量.
现在,关于你的代码 - 你的实现使用ref,它为你正在使用的每个可变变量创建一个小对象.另一种方法是创建一个具有多个可变字段的单个对象.使用记录,你可以写:
type ReadClosure = {
mutable c : char
mutable k : SomeType } // whatever type you use here
let rec read file includepath =
let state = { c = ' '; k = NONE }
// ...
let readc() =
state.c <- stream.Read() |> char
// etc...
Run Code Online (Sandbox Code Playgroud)
这可能会更有效率,因为您正在分配单个对象而不是几个对象,但我不认为差异会很明显.
关于您的代码还有一个令人困惑的事情 - 该stream值将在函数read返回后处理,因此调用stream.Read可能无效(如果您readc在read完成后调用).
let rec read file includepath =
let c = ref ' '
use stream = File.OpenText file
let readc() =
c := stream.Read() |> char
readc
let f = read a1 a2
f() // This would fail!
Run Code Online (Sandbox Code Playgroud)
我不太确定你是如何使用的readc,但这可能是一个需要考虑的问题.另外,如果你只是将它声明为一个辅助关闭,你可能会在没有闭包的情况下重写代码(或者使用尾递归显式地编写它,它被转换为带有可变变量的命令循环)以避免任何分配.
| 归档时间: |
|
| 查看次数: |
895 次 |
| 最近记录: |