F# 区分联合大小写到字符串的缓慢转换

Slow conversion of F# discriminated union case to string

我有大约 10 万个已区分的联合案例我必须转换为字符串,但它似乎非常慢。

作为比较,以下执行(在 F# 交互中)平均需要 3 秒:

open System

let buf = Text.StringBuilder()
let s = DateTime.Now

for i in 1 .. 100000 do
  Printf.bprintf buf "%A" "OtherFinancingInterest" //string
  buf.Length <- 0

printfn "elapsed : %0.2f" (DateTime.Now - s).TotalMilliseconds

虽然以下代码(也在 F# 交互中)在一分钟内执行...

open System

let buf = Text.StringBuilder()
let s = DateTime.Now

for i in 1 .. 100000 do
  Printf.bprintf buf "%A" OtherFinancingInterest //DU
  buf.Length <- 0

printfn "elapsed : %0.2f" (DateTime.Now - s).TotalMilliseconds

判别联合有 25 个值(结果仍然非常慢,两个案例大约需要 16 秒,但比 25 个要少)。知道这是 "normal" 还是我做错了什么?

非常感谢

%A 格式说明符可以很好地打印任何 F# 值。它使用反射来做到这一点。它只应真正用于调试目的,而不应用于正常的应用程序代码。

请注意,在您的第一个使用字符串的示例中使用 %s 会使其速度更快,因为在运行时不需要进行类型检查。

对于 DU,有一个 hack 可以用来使反射仅在应用程序加载时发生一次:

type FinancingInterest =
    | OtherFinancingInterest

open FSharp.Reflection
let private OtherFinancingInterestStringMap =
    FSharpType.GetUnionCases typeof<FinancingInterest>
    |> Array.map (fun c -> FSharpValue.MakeUnion(c, [||]) :?> FinancingInterest)
    |> Array.map (fun x -> x, sprintf "%A" x)
    |> Map.ofArray

type FinancingInterest with
    member this.AsString = OtherFinancingInterestStringMap |> Map.find this

您还可以将其与 %s 格式说明符一起使用:

Printf.bprintf buf "%s" OtherFinancingInterest.AsString

我在你的例子中有类似的时间,现在这个时间减少到 40 毫秒。

这只适用于所有 DU 案例都没有参数的情况。只要您尝试这样的操作,您就会在应用程序加载时遇到异常:

type FinancingInterest =
    | Foo of string
    | OtherFinancingInterest

说了这么多,我认为您最好编写一个简单的函数,将您的类型显式转换为字符串值,并在必要时完整地写出名称并重复。受歧视联合案例的名称通常不应被视为影响您的程序的数据。您通常希望能够安全地重命名案例名称而不影响运行时行为。