我有一个包含6列和678,552行的csv文件。遗憾的是,我无法共享任何数据样本,但类型很简单:int64
,int64
,date
,date
,string
,string
没有遗漏的价值。
使用read.table
在R中的数据框中加载此数据的时间:~3秒。
使用F#中的CsvFile.Load加载此数据的时间:~3秒。
在F#中以Deedle数据帧加载此数据的时间:~7秒。
添加inferTypes=false
并向Deedle Frame.ReadCsv
提供架构可将时间缩短至约3秒
在F#中使用CsvProvider加载此数据的时间: ~5分钟。
在我定义Schema
参数中的类型后,这5分钟甚至可以消除F#用来推断它们的时间。
据我所知,类型提供程序需要做的不仅仅是R或CsvFile.Load,以便将数据解析为正确的数据类型,但我对x100速度惩罚感到惊讶。更令人困惑的是Deedle加载数据所需的时间,因为它还需要推断类型和适当的转换,组合系列等。我实际上预计Deedle需要比CsvProvider更长的时间。
在this issue中,CsvProvider的不良性能是由大量列引起的,而不是我的情况。
我想知道我做错了什么,或者是否有任何方法可以加快速度。
只是为了澄清:创建提供者几乎是即时的。当我强制生成的序列由Seq.length df.Rows
实现时,fsharpi提示返回需要约5分钟。
我在Linux系统上,单声道v4.6.1上的F#v4.1。
以下是CsvProvider的代码
let [<Literal>] SEP = "|"
let [<Literal>] CULTURE = "sv-SE"
let [<Literal>] DATAFILE = dataroot + "all_diagnoses.csv"
type DiagnosesProvider = CsvProvider<DATAFILE, Separators=SEP, Culture=CULTURE>
let diagnoses = DiagnosesProvider()
EDIT1: 我添加了Deedle将数据加载到帧中的时间。
EDIT2:
添加了Deedle在inferTypes=false
和提供架构时所花费的时间。
此外,按照评论中的建议在CsvProvider中提供CacheRows=false
对加载时间没有明显影响。
EDIT3:
好的,我们到了某个地方。出于某些特殊原因,似乎Culture
是罪魁祸首。如果我省略这个参数,CsvProvider会在~7秒内加载数据。我不确定是什么原因引起的。我系统的区域设置是en_US。然而,数据来自瑞典语语言环境中的SQL Server,其中十进制数字由&#39;,&#39;分隔。而不是&#39;。这个特定的数据集没有任何小数,所以我可以完全省略Culture。然而,另一组有2个十进制列和超过1,000,000行。我的下一个任务是在目前尚无法使用的Windows系统上进行测试。
EDIT4: 问题似乎已经解决,但我仍然不明白是什么原因导致的。如果我改变文化&#34;全球&#34;通过做:
System.Globalization.CultureInfo.DefaultThreadCurrentCulture = CultureInfo("sv-SE")
System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo("sv-SE")
然后从CsvProvider中删除Culture="sv-SE"
参数,加载时间减少到约6秒,并正确解析小数。如果有人能对这种行为作出解释,我会将此保留。
答案 0 :(得分:6)
我正在尝试重现您所看到的问题,因为您无法共享我尝试生成一些测试数据的数据。但是,在我的机器上(.NET 4.6.2 F#4.1)我认为它不需要几分钟,需要几秒钟。
也许您可以尝试查看我的示例应用程序在您的设置中的执行情况,我们可以从中开始工作吗?
open System
open System.Diagnostics
open System.IO
let clock =
let sw = Stopwatch ()
sw.Start ()
fun () ->
sw.ElapsedMilliseconds
let time a =
let before = clock ()
let v = a ()
let after = clock ()
after - before, v
let generateDataSet () =
let random = Random 19740531
let firstDate = DateTime(1970, 1, 1)
let randomInt () = random.Next () |> int64 |> (+) 10000000000L |> string
let randomDate () = (firstDate + (random.Next () |> float |> TimeSpan.FromSeconds)).ToString("s")
let randomString () =
let inline valid ch =
match ch with
| '"'
| '\\' -> ' '
| _ -> ch
let c = random.Next () % 16
let g i =
if i = 0 || i = c + 1 then '"'
else 32 + random.Next() % (127 - 32) |> char |> valid
Array.init (c + 2) g |> String
let columns =
[|
"Id" , randomInt
"ForeignId" , randomInt
"BirthDate" , randomDate
"OtherDate" , randomDate
"FirstName" , randomString
"LastName" , randomString
|]
use sw = new StreamWriter ("perf.csv")
let headers = columns |> Array.map fst |> String.concat ";"
sw.WriteLine headers
for i = 0 to 700000 do
let values = columns |> Array.map (fun (_, f) -> f ()) |> String.concat ";"
sw.WriteLine values
open FSharp.Data
[<Literal>]
let sample = """Id;ForeignId;BirthDate;OtherDate;FirstName;LastName
11795679844;10287417237;2028-09-14T20:33:17;1993-07-21T17:03:25;",xS@ %aY)N*})Z";"ZP~;"
11127366946;11466785219;2028-02-22T08:39:57;2026-01-24T05:07:53;"H-/QA(";"g8}J?k~"
"""
type PerfFile = CsvProvider<sample, ";">
let readDataWithTp () =
use streamReader = new StreamReader ("perf.csv")
let csvFile = PerfFile.Load streamReader
let length = csvFile.Rows |> Seq.length
printfn "%A" length
[<EntryPoint>]
let main argv =
Environment.CurrentDirectory <- AppDomain.CurrentDomain.BaseDirectory
printfn "Generating dataset..."
let ms, _ = time generateDataSet
printfn " took %d ms" ms
printfn "Reading dataset..."
let ms, _ = time readDataWithTp
printfn " took %d ms" ms
0
性能数字(我桌面上的.NET462):
Generating dataset...
took 2162 ms
Reading dataset...
took 6156 ms
性能数字(Macbook Pro上的Mono 4.6.2):
Generating dataset...
took 4432 ms
Reading dataset...
took 8304 ms
<强>更新强>
事实证明,明确指定Culture
到CsvProvider似乎会降低性能。它可以是任何文化,不仅仅是sv-SE
,还有为什么?
如果检查提供者为快速和慢速情况生成的代码,则会注意到差异:
<强>快速强>
internal sealed class csvFile@78
{
internal System.Tuple<long, long, System.DateTime, System.DateTime, string, string> Invoke(object arg1, string[] arg2)
{
Microsoft.FSharp.Core.FSharpOption<string> fSharpOption = TextConversions.AsString(arg2[0]);
long arg_C9_0 = TextRuntime.GetNonOptionalValue<long>("Id", TextRuntime.ConvertInteger64("", fSharpOption), fSharpOption);
fSharpOption = TextConversions.AsString(arg2[1]);
long arg_C9_1 = TextRuntime.GetNonOptionalValue<long>("ForeignId", TextRuntime.ConvertInteger64("", fSharpOption), fSharpOption);
fSharpOption = TextConversions.AsString(arg2[2]);
System.DateTime arg_C9_2 = TextRuntime.GetNonOptionalValue<System.DateTime>("BirthDate", TextRuntime.ConvertDateTime("", fSharpOption), fSharpOption);
fSharpOption = TextConversions.AsString(arg2[3]);
System.DateTime arg_C9_3 = TextRuntime.GetNonOptionalValue<System.DateTime>("OtherDate", TextRuntime.ConvertDateTime("", fSharpOption), fSharpOption);
fSharpOption = TextConversions.AsString(arg2[4]);
string arg_C9_4 = TextRuntime.GetNonOptionalValue<string>("FirstName", TextRuntime.ConvertString(fSharpOption), fSharpOption);
fSharpOption = TextConversions.AsString(arg2[5]);
return new System.Tuple<long, long, System.DateTime, System.DateTime, string, string>(arg_C9_0, arg_C9_1, arg_C9_2, arg_C9_3, arg_C9_4, TextRuntime.GetNonOptionalValue<string>("LastName", TextRuntime.ConvertString(fSharpOption), fSharpOption));
}
}
<强>慢强>
internal sealed class csvFile@78
{
internal System.Tuple<long, long, System.DateTime, System.DateTime, string, string> Invoke(object arg1, string[] arg2)
{
Microsoft.FSharp.Core.FSharpOption<string> fSharpOption = TextConversions.AsString(arg2[0]);
long arg_C9_0 = TextRuntime.GetNonOptionalValue<long>("Id", TextRuntime.ConvertInteger64("sv-SE", fSharpOption), fSharpOption);
fSharpOption = TextConversions.AsString(arg2[1]);
long arg_C9_1 = TextRuntime.GetNonOptionalValue<long>("ForeignId", TextRuntime.ConvertInteger64("sv-SE", fSharpOption), fSharpOption);
fSharpOption = TextConversions.AsString(arg2[2]);
System.DateTime arg_C9_2 = TextRuntime.GetNonOptionalValue<System.DateTime>("BirthDate", TextRuntime.ConvertDateTime("sv-SE", fSharpOption), fSharpOption);
fSharpOption = TextConversions.AsString(arg2[3]);
System.DateTime arg_C9_3 = TextRuntime.GetNonOptionalValue<System.DateTime>("OtherDate", TextRuntime.ConvertDateTime("sv-SE", fSharpOption), fSharpOption);
fSharpOption = TextConversions.AsString(arg2[4]);
string arg_C9_4 = TextRuntime.GetNonOptionalValue<string>("FirstName", TextRuntime.ConvertString(fSharpOption), fSharpOption);
fSharpOption = TextConversions.AsString(arg2[5]);
return new System.Tuple<long, long, System.DateTime, System.DateTime, string, string>(arg_C9_0, arg_C9_1, arg_C9_2, arg_C9_3, arg_C9_4, TextRuntime.GetNonOptionalValue<string>("LastName", TextRuntime.ConvertString(fSharpOption), fSharpOption));
}
}
更具体地说,这是区别:
// Fast
TextRuntime.ConvertDateTime("", fSharpOption), fSharpOption)
// Slow
TextRuntime.ConvertDateTime("sv-SE", fSharpOption), fSharpOption)
当我们指定文化时,会将其传递给ConvertDateTime
,然后将其转发给GetCulture
static member GetCulture(cultureStr) =
if String.IsNullOrWhiteSpace cultureStr
then CultureInfo.InvariantCulture
else CultureInfo cultureStr
这意味着对于默认情况,我们使用CultureInfo.InvariantCulture
,但对于每个字段和行的任何其他情况,我们正在创建CultureInfo
对象。缓存可以完成,但事实并非如此。创建过程本身似乎没有花费太多时间,但每当我们使用新的CultureInfo
对象进行解析时会发生一些事情。
在DateTime
中解析FSharp.Data
基本上就是这个
let dateTimeStyles = DateTimeStyles.AllowWhiteSpaces ||| DateTimeStyles.RoundtripKind
match DateTime.TryParse(text, cultureInfo, dateTimeStyles) with
因此,让我们进行性能测试,我们使用缓存的CultureInfo
对象和另一个我们每次创建一个对象的对象。
open System
open System.Diagnostics
open System.Globalization
let clock =
let sw = Stopwatch ()
sw.Start ()
fun () ->
sw.ElapsedMilliseconds
let time a =
let before = clock ()
let v = a ()
let after = clock ()
after - before, v
let perfTest c cf () =
let dateTimeStyles = DateTimeStyles.AllowWhiteSpaces ||| DateTimeStyles.RoundtripKind
let text = DateTime.Now.ToString ("", cf ())
for i = 1 to c do
let culture = cf ()
DateTime.TryParse(text, culture, dateTimeStyles) |> ignore
[<EntryPoint>]
let main argv =
Environment.CurrentDirectory <- AppDomain.CurrentDomain.BaseDirectory
let ct = "sv-SE"
let cct = CultureInfo ct
let count = 10000
printfn "Using cached CultureInfo object..."
let ms, _ = time (perfTest count (fun () -> cct))
printfn " took %d ms" ms
printfn "Using fresh CultureInfo object..."
let ms, _ = time (perfTest count (fun () -> CultureInfo ct))
printfn " took %d ms" ms
0
.NET 4.6.2上的性能数字F#4.1:
Using cached CultureInfo object...
took 16 ms
Using fresh CultureInfo object...
took 5328 ms
因此,在CultureInfo
中缓存FSharp.Data
对象时,如果指定了culture,则应显着提高CsvProvider
性能。
答案 1 :(得分:2)
问题是由CsvProvider没有记住明确设置的Culture
引起的。 this拉取请求解决了这个问题。