为什么通过string或ToString()将F#区分的联合转换为字符串这么慢?

时间:2019-02-15 11:35:57

标签: f# f#-data

是否有一种快速的方法将已区分的并集转换为字符串?

我试图弄清楚为什么要使用各种方法将大量记录保存到csv文件中要花费几个小时。我尝试了CsvProvider.Save,sprintf,字符串生成器等,但都非常慢。我认为我已将问题追溯到有区别的联合体类型转换。

下面的示例说明了该问题。有没有更好的方法,还是我的“手动转换”是最好的选择。

#time
open System

type Field = | Ying | Yang
let manual = function | Ying -> "Ying" | Yang -> "Yang"

// Discriminated Union versions

[for i = 0 to 100000 do yield (Ying).ToString()] |> ignore
//Real: 00:00:12.963, CPU: 00:00:13.281, GC gen0: 10, gen1: 0, gen2: 0

[for i = 0 to 100000 do yield (Ying) |> manual] |> ignore
//Real: 00:00:00.004, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0

// Others for comparison

[for i = 0 to 100000 do yield (1).ToString()] |> ignore
//Real: 00:00:00.011, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0
[for i = 0 to 100000 do yield (1.0).ToString()] |> ignore
//Real: 00:00:00.054, CPU: 00:00:00.062, GC gen0: 0, gen1: 0, gen2: 0
[for i = 0 to 100000 do yield (1.0m).ToString()] |> ignore
//Real: 00:00:00.014, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0


2 个答案:

答案 0 :(得分:6)

转换为字符串很慢,因为DU案例名称实际上是代码的一部分,而不是程序的数据。将其转换为字符串实际上是一种元编程技术,必须超出程序正常运行时的范围,即.NET中的反射。

通常,标识符名称不会影响程序的运行是一件好事,因为这意味着像重命名标识符这样的重构是完全安全的。

但是,如果您真的想做到这一点并使其快速进行,我认为最实用的解决方案是使用备忘录:

let memoize fn =
    let cache = System.Collections.Concurrent.ConcurrentDictionary<'a, 'b>()
    (fun x -> cache.GetOrAdd(x, fun _ -> fn x))

let showField : Field -> string = memoize string

memoize函数采用一个函数并创建该函数的版本,该版本将缓存每个输入的输出。在每个DU案例中,showField函数现在应该与您的manual函数差不多快。

答案 1 :(得分:0)

如果您对格式不太挑剔,也许可以使用NewtonSoft.Json序列化集合。

或者您可以尝试将每个DU值附加到StringBuilder,然后在StringBuilder上调用ToString以获取完整的字符串。