dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.87k stars 781 forks source link

Tokenizing massive F# file leads to LOH allocations and UI freezes in VS #9069

Open cartermp opened 4 years ago

cartermp commented 4 years ago
  1. Open the F# codebase
  2. Open some F# compiler files (this is just to get memory usage up to where it normally is when working in the compiler)
  3. Open ComparersRegression.fs (nearly 60k LoC)
  4. Scroll around, note that memory pressure is high in VS and the UI freezes often

From a 50ish second sample:

image

These LOH allocs are all coming from tokenizing the source, the SourceTextData.extendTo function: https://github.com/dotnet/fsharp/blob/be621d8725e3ea88cb832f4d457bc0bf377172ed/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs#L419

image

And tokenizing source itself is the lion's share of all allocs. Note the LOH part of it:

image

TIHan commented 4 years ago

At the moment, Tokenizer seems to be the main problem as its at the top.

The FSharpTokenInfo allocations are from FCS's service lexing API. This object is rather large:

type FSharpTokenInfo = {
    LeftColumn: int
    RightColumn: int
    ColorClass: FSharpTokenColorKind
    CharClass: FSharpTokenCharKind
    FSharpTokenTriggerClass: FSharpTokenTriggerClass
    Tag: int
    TokenName: string
    FullMatchedLength: int }

This is a heavy weight object for something to represent a token.

This https://github.com/dotnet/fsharp/blob/master/src/fsharp/service/ServiceLexing.fs#L1016 tried to address some of these issues, but the Lexer API that uses it doesn't keep track of previous state. The previous state is very important for the open document.

abelbraaksma commented 4 years ago

Before FSharpTokenInfo is even used, I took a snapshot before coloring was completed (plenty of time), from which we can get some pretty nice modern art (with dotMemory):

image

Here:

I know this is all AST, and likely very hard to optimize, but these three trees together take some 60% of the memory during (I think) the lexing part.

Do note that this colorful chart is before colorization of the ComparersRegression.fs even took place.

abelbraaksma commented 4 years ago

I thought "what if I take that file and create an application with just that single file". This would allow a more targeted analysis, but wasn't easy using Visual Studio, as it kept disappearing on me (meaning: literally disappearing, no crash window or auto-restart). The eventlog showed a hint:

Application: devenv.exe
Framework Version: v4.0.30319
Description: The application requested process termination through System.Environment.FailFast(string message).
Message: System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.CodeAnalysis.Editor.Implementation.Structure.AbstractStructureTaggerProvider`1.<ProduceTagsAsync>d__6.MoveNext()
Stack:
   at System.Environment.FailFast(System.String, System.Exception)
   at Microsoft.CodeAnalysis.FailFast.OnFatalException(System.Exception)
   at Microsoft.CodeAnalysis.ErrorReporting.FatalError.Report(System.Exception, System.Action`1<System.Exception>)
   at Microsoft.CodeAnalysis.ErrorReporting.FatalError.ReportUnlessCanceled(System.Exception)
   at Microsoft.CodeAnalysis.Editor.Implementation.Structure.AbstractStructureTaggerProvider`1+<ProduceTagsAsync>d__6[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].MoveNext()
   at Microsoft.CodeAnalysis.Editor.Implementation.Structure.AbstractStructureTaggerProvider`1+<ProduceTagsAsync>d__6[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].MoveNext()
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.InvokeMoveNext(System.Object)
   at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.Run()
   at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(System.Action, Boolean, System.Threading.Tasks.Task ByRef)
   at System.Threading.Tasks.Task.FinishContinuations()
   at System.Threading.Tasks.Task.FinishStageThree()
   at System.Threading.Tasks.Task`1[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].TrySetResult(System.__Canon)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].SetResult(System.__Canon)
   at Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Structure.FSharpBlockStructureService+<GetBlockStructureAsync>d__4.MoveNext()
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.InvokeMoveNext(System.Object)
   at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.Run()
   at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(System.Action, Boolean, System.Threading.Tasks.Task ByRef)
   at System.Threading.Tasks.Task.FinishContinuations()
   at System.Threading.Tasks.Task.FinishStageThree()
   at System.Threading.Tasks.Task`1[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].TrySetResult(System.__Canon)
   at System.Threading.Tasks.TaskCompletionSource`1[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].TrySetResult(System.__Canon)
   at Microsoft.VisualStudio.FSharp.Editor.RoslynHelpers+StartAsyncAsTask@113-4[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].Invoke(System.Exception)
   at Microsoft.FSharp.Control.AsyncPrimitives+StartWithContinuations@916-2.Invoke(System.Runtime.ExceptionServices.ExceptionDispatchInfo)
   at Microsoft.FSharp.Control.Trampoline.Execute(Microsoft.FSharp.Core.FSharpFunc`2<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Control.AsyncReturn>)
   at Microsoft.FSharp.Control.TrampolineHolder.ExecuteWithTrampoline(Microsoft.FSharp.Core.FSharpFunc`2<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Control.AsyncReturn>)
   at Microsoft.FSharp.Control.AsyncPrimitives.continuation@942[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]](Microsoft.FSharp.Control.AsyncActivation`1<System.__Canon>, Boolean, System.Threading.Tasks.Task`1<System.__Canon>)
   at Microsoft.FSharp.Control.AsyncPrimitives+taskContinueWith@956[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].Invoke(System.Threading.Tasks.Task`1<System.__Canon>)
   at System.Threading.Tasks.ContinuationTaskFromResultTask`1[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].InnerInvoke()
   at System.Threading.Tasks.Task.Execute()
   at System.Threading.Tasks.Task.ExecutionContextCallback(System.Object)
   at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef)
   at System.Threading.Tasks.Task.ExecuteEntry(Boolean)
   at System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
cartermp commented 4 years ago

Similar to https://github.com/dotnet/fsharp/issues/9575 but a different service, here: http://sourceroslyn.io/#Microsoft.CodeAnalysis.EditorFeatures.Wpf/Structure/AbstractStructureTaggerProvider.cs,a40a8aa57739df85

cartermp commented 4 years ago

@abelbraaksma I can't reproduce this with an internal preview build of VS with a new console app. The stack trace is coming from structured guidelines, where we pass back an empty array if there are no structured guidelines to yield back. Somehow something else is null unexpectedly

abelbraaksma commented 4 years ago

I'll try to reliably repro it, it may have come from interaction with the IDE in that file (I scrolled through while there were still errors, in part because it later appeared that indentation after copy paste was wrong).

abelbraaksma commented 4 years ago

Another example file in the FSharp.sln that shows this behavior is pars.fs. It is considerably smaller, but also makes VS sluggish and has memory rising through the roof (though in this case it appears that the sluggishness is not only caused by memory, it's certainly not as much as for this case, but a good example of "another F# thing that slows down VS").