New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
francotiveron opened this Issue Apr 23, 2018 · 6 comments

Comments

Projects
None yet
2 participants
@francotiveron

francotiveron commented Apr 23, 2018

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:

  • Is this normal or am I doing something wrong?
  • Are there guidelines to achieve a faster response?

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

This comment has been minimized.

Contributor

dsyme commented May 24, 2018

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

This comment has been minimized.

Contributor

dsyme commented May 24, 2018

@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 added a commit to dsyme/FSharp.TypeProviders.SDK that referenced this issue May 24, 2018

@dsyme

This comment has been minimized.

Contributor

dsyme commented May 24, 2018

@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

This comment has been minimized.

Contributor

dsyme commented May 25, 2018

@francotiveron Thanks for reporting this problem. Fix is here: #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

This comment has been minimized.

francotiveron commented May 31, 2018

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

@dsyme

This comment has been minimized.

Contributor

dsyme commented Jun 1, 2018

Great, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment