demetrixbio / FSharp.Data.Npgsql

F# type providers to support statically typed access to input parameters and result set of sql statement in idiomatic F# way. Data modifications via statically typed tables.
Apache License 2.0
127 stars 16 forks source link

Quotation optimizations #65

Closed kerams closed 4 years ago

kerams commented 4 years ago

I don't believe it's necessary to use a dictionary with design time property names, because we can rely on runtime column verification. Therefore, records can be erased to an array, providing an order of magnitude better runtime performance at a fraction of the memory footprint, and the quotations end up a lot simpler. It would be great if you could measure if that makes any difference during design time.

Furthermore, the rowMapping quotation could possibly be removed in the future completely, since it's based on result set definitions, which are passed to the runtime bit too

kerams commented 4 years ago
[<BenchmarkAttribute>]
member _.QueryBefore () =
    use cmd = DvdRental.CreateCommand<"select * from film">(dvdRental)
    cmd.Execute()

[<BenchmarkAttribute>]
member _.AccessBefore () =
    for row in rows do
        row.film_id |> ignore
BenchmarkDotNet=v0.12.0, OS=Windows 10.0.19041
AMD Ryzen 7 3700X, 1 CPU, 16 logical and 8 physical cores
.NET Core SDK=3.1.101
  [Host]     : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT DEBUG
  DefaultJob : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
QueryBefore 8.631 ms 0.1713 ms 0.3613 ms - - - 6.06 MB
QueryAfter 7.938 ms 0.0464 ms 0.0362 ms - - - 3.76 MB
AccessBefore 68.36 us 1.577 us 1.619 us - - - -
AccessAfter 16.18 us 0.392 us 1.149 us - - - -
AccessAfterV2 3.340 us 0.0788 us 0.0737 us - - - -

The Query benchmark highlights the difference between runtime allocations when selecting 1000 rows. Speed is irrelevant, because it depends on Postgres.

The Access benchmark shows the speed of accessing a single property a 1000 times.

Wish I could benchmark the design time experience in VS, but I don't know how.

daz10000 commented 4 years ago

We have a fairly large project. We can probably open up in vscode and see how long it takes till type inference finishes. We did manage to profile the type provider at one point and the main time was lost traversing huge trees of quotations. The other big problem was that we were recreating the provider ins bunch of places in the code but that was an eat fix.

Von meinem iPhone gesendet

Am 01.03.2020 um 4:39 AM schrieb kerams notifications@github.com:

 [] member _.Before () = use cmd = DvdRental.CreateCommand<"select * from film">(dvdRental) cmd.Execute() BenchmarkDotNet=v0.12.0, OS=Windows 10.0.19041 AMD Ryzen 7 3700X, 1 CPU, 16 logical and 8 physical cores .NET Core SDK=3.1.101 [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT DEBUG DefaultJob : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated Before 8.631 ms 0.1713 ms 0.3613 ms - - - 6.06 MB After 7.938 ms 0.0464 ms 0.0362 ms - - - 3.76 MB This is the runtime difference of selecting a 1000 rows before and after the optimization is applied. The speed is not really relevant as it largely depends on Postgres.

Wish I could benchmark the design time experience in VS, but I don't know how.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or unsubscribe.

kerams commented 4 years ago

I've updated the benchmark having optimized array access further. We're now looking at a 20x speedup, which is what I had in mind originally.

This quotation looks really nasty and is constructed for every column. @daz10000, I keep looking at it and, correct my if I'm wrong, have a feeling that all the ExtendedProperties and DateTimeMode nonsense is only used for ResultType.DataTable. In other words, for records, the entire thing could be reducible to new DataColumn( %%Expr.Value(this.Name), Type.GetType( typeName, throwOnError = true)), potentially yielding a massive gain given how often the quotation is used.

kerams commented 4 years ago

Alright, I did just that. The tests pass, but a couple more sets of eyes would be nice.