f# mono f#-data

f# - FSharp.Data CsvProvider rendimiento



mono f#-data (2)

Tengo un archivo csv con 6 columnas y 678,552 filas. Lamentablemente, no puedo compartir ninguna muestra de datos pero los tipos son sencillos: int64 , int64 , date , date , string , string y no hay valores perdidos.

Tiempo para cargar estos datos en un marco de datos en R usando read.table : ~ 3 segundos.

Es hora de cargar estos datos usando CsvFile.Load en F #: ~ 3 segundos.

Es hora de cargar estos datos en un marco de datos de Deedle en F #: ~ 7 segundos.

Agregar inferTypes=false y proporcionar un esquema a Deedle''s Frame.ReadCsv reduce el tiempo a ~ 3 segundos

Es hora de cargar estos datos usando CsvProvider en F #: ~ 5 minutos .

Y estos 5 minutos son incluso después de que defino los tipos en el parámetro Schema , presumiblemente eliminando el tiempo que F # usaría para inferirlos.

Entiendo que el proveedor de tipos necesita hacer mucho más que R o CsvFile.Load para analizar los datos en el tipo de datos correcto, pero me sorprende la penalización de velocidad x100. Aún más confuso es el tiempo que toma Deedle para cargar los datos, ya que también necesita inferir tipos y emitir adecuadamente, organizar en Series, etc. De hecho, habría esperado que Deedle tardara más que CsvProvider.

En esta edición, el mal rendimiento de CsvProvider fue causado por un gran número de columnas, que no es mi caso.

Me pregunto si estoy haciendo algo mal o si hay alguna manera de acelerar un poco las cosas.

Solo para aclarar: crear el proveedor es casi instantáneo. Es cuando Seq.length df.Rows que la secuencia generada se realice por Seq.length df.Rows que tardan ~ 5 minutos para que vuelva el prompt de fsharpi.

Estoy en un sistema Linux, F # v4.1 en mono v4.6.1.

Aquí está el código para 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: agregué el tiempo que toma Deedle para cargar los datos en un marco.

EDIT2: se agregó el tiempo que toma Deedle si inferTypes=false y se proporciona un esquema.

Además, el suministro de CacheRows=false en CsvProvider como se sugiere en los comentarios no tiene un efecto perceptible en el tiempo de carga.

EDIT3: Ok, estamos llegando a algún lado. Por alguna razón peculiar parece que la Culture es la culpable. Si omito este argumento, CsvProvider carga los datos en ~ 7 segundos. No estoy seguro de qué podría estar causando esto. La configuración regional de mi sistema es en_US. Sin embargo, los datos provienen de un servidor SQL en la configuración sueca donde los dígitos decimales están separados por '','' en lugar de ''.''. Este conjunto de datos en particular no tiene decimales, por lo que puedo omitir Cultura por completo. Sin embargo, otro conjunto tiene 2 columnas decimales y más de 1,000,000 filas. Mi siguiente tarea es probar esto en un sistema Windows que no tengo disponible en este momento.

EDIT4: El problema parece resuelto, pero todavía no entiendo qué lo causa. Si cambio la cultura "globalmente" haciendo:

System.Globalization.CultureInfo.DefaultThreadCurrentCulture = CultureInfo("sv-SE") System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo("sv-SE")

y luego elimine el argumento Culture="sv-SE" del CsvProvider, el tiempo de carga se reduce a ~ 6 segundos y los decimales se analizan correctamente. Lo dejo abierto en caso de que alguien pueda dar una explicación de este comportamiento.


El problema se debe a que CsvProvider no ha memorizado el Culture explícitamente establecido. El problema fue abordado por esta solicitud de extracción.


Intento reproducir el problema que estás viendo, ya que no puedes compartir los datos. Intenté generar algunos datos de prueba. Sin embargo, en mi máquina (.NET 4.6.2 F # 4.1) no veo que tome unos minutos, lleva unos segundos.

Quizás pueda intentar ver cómo funciona mi aplicación de muestra en su configuración y podemos trabajar a partir de eso.

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

Los números de rendimiento (.NET462 en mi escritorio):

Generating dataset... took 2162 ms Reading dataset... took 6156 ms

Los números de rendimiento (Mono 4.6.2 en mi Macbook Pro):

Generating dataset... took 4432 ms Reading dataset... took 8304 ms

Actualizar

Resulta que especificar Culture to CsvProvider explícitamente parece degradar el rendimiento mucho. Puede ser cualquier cultura, no solo sv-SE pero ¿por qué?

Si uno verifica el código que el proveedor genera para los casos rápidos y lentos, notará una diferencia:

Rápido

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)); } }

Lento

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)); } }

Más específico esta es la diferencia:

// Fast TextRuntime.ConvertDateTime("", fSharpOption), fSharpOption) // Slow TextRuntime.ConvertDateTime("sv-SE", fSharpOption), fSharpOption)

Cuando especificamos una cultura, esto se pasa a ConvertDateTime que lo reenvía a GetCulture

static member GetCulture(cultureStr) = if String.IsNullOrWhiteSpace cultureStr then CultureInfo.InvariantCulture else CultureInfo cultureStr

Esto significa que, para el caso predeterminado, usamos CultureInfo.InvariantCulture pero para cualquier otro caso para cada campo y fila estamos creando un objeto CultureInfo . El almacenamiento en caché se puede hacer, pero no es así. El proceso de creación en sí no parece tomar demasiado tiempo, pero sucede algo cuando estamos analizando con un nuevo objeto CultureInfo cada vez.

El análisis de DateTime en FSharp.Data esencialmente es esto

let dateTimeStyles = DateTimeStyles.AllowWhiteSpaces ||| DateTimeStyles.RoundtripKind match DateTime.TryParse(text, cultureInfo, dateTimeStyles) with

Así que hagamos una prueba de rendimiento en la que usemos un objeto CultureInfo caché y otro en el que creamos uno cada vez.

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

Números de rendimiento en .NET 4.6.2 F # 4.1:

Using cached CultureInfo object... took 16 ms Using fresh CultureInfo object... took 5328 ms

Por lo tanto, parece almacenar en caché el objeto CultureInfo en FSharp.Data debería mejorar significativamente el rendimiento de CsvProvider cuando se especifica culture.