fsprojects / FSharp.TypeProviders.SDK

The SDK for creating F# type providers
https://fsprojects.github.io/FSharp.TypeProviders.SDK/
MIT License
298 stars 94 forks source link

F# Type Provider slows down intellisense in Visual Studio 2017 #220

Closed francotiveron closed 6 years ago

francotiveron commented 6 years ago

https://stackoverflow.com/questions/49961630/f-type-provider-slows-down-intellisense-in-visual-studio-2017

I have a very simple type provider; all types are erased, the provided type has 2000 int readonly properties Tag1..Tag2000

let ns = "MyNamespace"
let asm = Assembly.GetExecutingAssembly()

let private newProperty t name getter isStatic = ProvidedProperty(name, t, getter, isStatic = isStatic)
let private newStaticProperty t name getter = newProperty t name (fun _ -> getter) true
let private newInstanceProperty t name getter = newProperty t name (fun _ -> getter) false
let private addStaticProperty t name getter (``type``:ProvidedTypeDefinition) = ``type``.AddMember (newStaticProperty t name getter); ``type``
let private addInstanceProperty t name getter (``type``:ProvidedTypeDefinition) = ``type``.AddMember (newInstanceProperty t name getter); ``type``

[<TypeProvider>]
type TypeProvider(config : TypeProviderConfig) as this = 
    inherit TypeProviderForNamespaces(config)

    let provider = ProvidedTypeDefinition(asm, ns, "Provider", Some typeof<obj>, hideObjectMethods = true)
    let tags = ProvidedTypeDefinition(asm, ns, "Tags", Some typeof<obj>, hideObjectMethods = true)           
    do [1..2000] |> Seq.iter (fun i -> addInstanceProperty typeof<int> (sprintf "Tag%d" i) <@@ i @@> tags |> ignore)

    do provider.DefineStaticParameters([ProvidedStaticParameter("Host", typeof<string>)], fun name args ->
        let provided = ProvidedTypeDefinition(asm, ns, name, Some typeof<obj>, hideObjectMethods = true)
        addStaticProperty tags "Tags" <@@ obj() @@> provided |> ignore
        provided
    )

    do this.AddNamespace(ns, [provider; tags])

Then a test project with two modules in separate files:

module Common
open MyNamespace

type Provided = Provider<"">
let providedTags = Provided.Tags

type LocalTags() = 
    member this.Tag1 with get() : int = 1
    member this.Tag2 with get() : int = 2
.
.
    member this.Tag1999 with get() : int = 1999
    member this.Tag2000 with get() : int = 2000

let localTags = LocalTags()
module Tests
open Common
open Xunit

[<Fact>]
let ProvidedTagsTest () =
    Assert.Equal<int>(providedTags.Tag1001, 1001)

[<Fact>]
let LocalTagsTest () =
    Assert.Equal<int>(localTags.Tag100, 100)

Everything works as expected (tests execution included). The problem I have is with the design time behavior inside Visual Studio, while I write code. I expect to have some overhead due to the type provider, but the slowness seems frankly excessive. The times reported below are in seconds and refer to the time measured from pushing the dot (.) key until the intellisense property list appears on the screen

  1. providedTags. -> 15
  2. localTags. -> 5

If I comment out or remove the first test code lines (so to eliminate any references to the provided stuff), then I get

  1. localTags. -> immediate

If the number of properties is greater, the time seems to increase exponentially, not linearly, so that at 10000 it becomes minutes.

Questions are:

If someone is curious about why I need so many properties, I am trying to supply an instrument to data analysts so that they can write F# scripts and get data out of an historian database with more than 10000 tags in its schema.

dsyme commented 6 years ago

If the number of properties is greater, the time seems to increase exponentially, not linearly, so that at 10000 it becomes minutes.

Thanks for the bug report. 10000 probably indicates quadratic not exponential. WIll try to repro

dsyme commented 6 years ago

@francotiveron Was your type provider in a .NET SDK-style (new-style) project, like this used in "examples" in this SDK? A bad bug in performance of .NET SDK-style proects has recently been fixed which may be relevant here - basically all .NET SDK-style projects were being re-typechecked on every key press. @TIHan did the fix. IIRC the fix will be in Visual Studio 15..x or 15.8

I also did a quick profiling run to see if anything specific in the autocomplete or TPSDK implementation stands out. Here are the top ones:

Function Name   Total CPU [ms, %]   Self CPU [ms, %]    Module
| - ProviderImplementation.ProvidedTypes.Utils::memberBinds 33751 (37.97%)  11183 (12.58%)  StressProvider.dll
| - <StartupCode$StressProvider>.$ProvidedTypes+GetMethods@1397::Invoke 49311 (55.47%)  6776 (7.62%)    StressProvider.dll
| - [External Call] System.String.Equals(System.String, System.String)$##60004B9    3681 (4.14%)    3681 (4.14%)    Multiple modules
| - [External Call] System.Enum.HasFlag(System.Enum)$##6000DF4  3207 (3.61%)    3207 (3.61%)    mscorlib.ni.dll
| - [External Call] Microsoft.FSharp.Core.FSharpOption`1[System.__Canon].Some(System.__Canon)$##6000170 3181 (3.58%)    3181 (3.58%)    Multiple modules
| - [External Call] Microsoft.FSharp.Collections.ArrayModule.Choose[System.__Canon,System.__Canon](Microsoft.FSharp.Core.FSharpFunc`2<System.__Canon,Microsoft.FSharp.Core.FSharpOption`1<System.__Canon>>, System.__Canon[])$##6001AC1 69204 (77.84%)  2296 (2.58%)    FSharp.Core.ni.dll
| - <StartupCode$StressProvider>.$ProvidedTypes+xs@1426-1::Invoke   6113 (6.88%)    2086 (2.35%)    StressProvider.dll
| - [External Call] System.Collections.Generic.List`1[System.__Canon].ToArray()$##60039D3   1577 (1.77%)    1577 (1.77%)    Multiple modules
| - ProviderImplementation.ProvidedTypes.ProvidedMethod::get_Attributes 1305 (1.47%)    1299 (1.46%)    StressProvider.dll
dsyme commented 6 years ago

@francotiveron There is definitely quadratic behaviour here, I can see at least one source of it, where GetMethodImpl repeatedly re-evaluates the full set of methods in a ProvidedTypeDefinition. #228 has some commented-out attempts to deal with it by using a lookaside table for the various member lookup operations, however it seemed to make things worse, not better so it hasn't been activated yet.

dsyme commented 6 years ago

@francotiveron Thanks for reporting this problem. Fix is here: https://github.com/fsprojects/FSharp.TypeProviders.SDK/pull/229

I had also seen slow autocomplete in the very large collection of members in the World Bank type provider in FSharp.Data and I think this was the cause.

francotiveron commented 6 years ago

Huge improvement, now I get the list displayed in 4s with 10000 tags. Thanks

dsyme commented 6 years ago

Great, thanks.