-
Notifications
You must be signed in to change notification settings - Fork 846
Optimize simplify name analyzer #2254
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
Optimize simplify name analyzer #2254
Conversation
add cache
|
These speed improvements look great - but over 400 lines of code added with no tests? :( |
| let getProjectInfoManager (document: Document) = document.Project.Solution.Workspace.Services.GetService<FSharpCheckerWorkspaceService>().ProjectInfoManager | ||
| let getChecker (document: Document) = document.Project.Solution.Workspace.Services.GetService<FSharpCheckerWorkspaceService>().Checker | ||
| let getPlidLength (plid: string list) = (plid |> List.sumBy String.length) + plid.Length | ||
| static let cache = ConditionalWeakTable<DocumentId, TextVersionHash * ImmutableArray<Diagnostic>>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious if the cache entry deleted when the document is closed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@smoothdeveloper read what this ConditionalWeakTable is :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
automatically removes the key/value entry as soon as no other references to a key exist outside the table
That's nice, didn't know about that class, thanks for the pointer. So I guess the answer is it doesn't get deleted when document is closed but when it is garbage collected.
It's all copied with adjustments, and the code is not used anywhere except this analyzer. |
|
so how much faster is it now? |
|
@forki 29 seconds => 800 ms ~= 36 times faster. |
|
Cool. Now do that to the rest of the compiler :P
Am 14.01.2017 4:45 nachm. schrieb "Vasily Kirichenko" <
notifications@github.com>:
… @forki <https://github.com/forki> 29 seconds => 800 ms ~= 36 times faster.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2254 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AADgNMuGxhqhw8aOJ2FMdLNqn-7fY2e3ks5rSO2igaJpZM4Ljlnf>
.
|
|
I've not optimized the compiler, I copied functions and make them fast for my particular case. In short, the existing functions return all items resolvable in a scope, my functions just check that an already resolved item can be resolved being prefixed with a given long ident. It's faster because 1. I pull the available items lazily, until the needed item is found 2. I know the kind of item (ctor, prop, ns or module, etc.) and search only for items of same kind. So these optimizations cannot be used in compiler. |
|
I got that and was obviously joking.
Am 14.01.2017 4:53 nachm. schrieb "Vasily Kirichenko" <
notifications@github.com>:
… I've not optimized the compiler, I copied functions and make them fast for
my particular case. In short, the existing functions return all items
resolvable in a scope, my functions just check that an already resolved
item can be resolved being prefixed with a given long ident. It's faster
because 1. I pull the available items lazily, until the needed item is
found 2. I know the kind of item (ctor, prop, ns or module, etc.) and
search only for items of same kind. So these optimizations cannot be used
in compiler.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2254 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AADgNJTuW-bkndIePQG7UPbQDBinivWPks5rSO9ygaJpZM4Ljlnf>
.
|
|
@vasily-kirichenko is it wroth to put some comments on the copied functions, to mention the place they are copied from? I'm thinking that if down the road the original functions change, having that mention would help figure out if catch-up is necessary in those customized functions. |
a80e607 to
c8edcc3
Compare
|
A few IDE tests failes |
| let ctorInfos = GetIntrinsicConstructorInfosOfType ncenv.InfoReader m typ | ||
| if isInterfaceTy g typ && isNil ctorInfos then | ||
| let ctorInfos = GetIntrinsicConstructorInfosOfType ncenv.InfoReader m typ | ||
| if isNil ctorInfos && isInterfaceTy g typ then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we should read the whole compiler code to fix such stupid perf penalties.
Reformat ResolveObjectConstructorPrim
|
@dsyme when memory pressure is here, even tooltips can appear with a very long delay, even though code has not changed at all, i.e.:
I think that's because either generating the second tooltip triggered blocking GC or a weak cache data for current file have become available for GC between the first and second tooltips, so the file was rechecked. |
My impression is that we must avoid memory pressure > 2GB at all costs, even if some features simply don't work in reasonable time. Once you go above 2GB, nothing works anymore until you restart. |
|
In my case (with VS2015 + VFPT), I've seen that things may stall or not disregarding the memory occupied by devenv.exe (looking at Working Set (Memory) in task manager). I've seen process with 2.7gb still doing fine and process with 2gb being unworkable, and I can't really identify why. |
It's not about features, it's about FCS caches. Features themselves do not add anything to gen 2. I was thinking about an external process. Maybe it's better to move some FCS internal caches there, not FCS itself, keeping the current VS <-> FCS API untouched. |
But features do cause information to be required more often. This adds requests to FCS (potentially lengthening the request queue, especially if cancellation is not performed), and may add to compute/allocate/populate/collect churn. The use of a feature can trigger a Gen2 collection in the worst case, while turning off a feature can only improve things. Also for large files, I believe we reguarly get Gen2 collections even within the typecheck phase of that file itself (even if no caching occurs). So any feature requiring CheckFile results may trigger Gen2. I'm just concerned we're going to see Gen2 collections or blocking UI pauses triggered by features we can't turn off. When using VFPT I regularly turned off all but a couple of the most important features. Advanced F# programmers don't need to have their code constantly re-analyzed every few seconds for removing "new" or the like.... :) But they do need to have the tools work over 100,000s lines of code :) |
|
Just as an FYI - a 32-bit process on a 64-bit OS will get a full 4GB of address space, so yes, it's possible to use 4GB of Virtual Memory. Note that things like task manager don't actually report the total VM though - they leave out some categories of memory. To get an accurate picture of VM, use something like vmmap. For people running 32-bit processes on 32-bit machines, they get either 2 or 3 GB of VM, depending on OS Version and whether the app declares itself large address aware (VS does). The OS always keeps at least 1GB for kernel address space though. Note however that even with 4GB of address space, you can run into issues with fragmentation on large allocations - the OS (or the CLR) may not have enough contiguous address space to perform an allocation. Note also that everything I said above was about virtual memory. The amount of physical memory the machine has has no bearing on it - if the machine doesn't have enough physical memory for the virtual memory, some will be swapped to disk. If the machine has more physical memory, it will not be accessible to the process (except through transparent OS optimizations like file-system caching). Address space/VM really come down to pointer-size. A 32-bit process uses 32-bit pointers, which means they can only address 4GB of memory, no matter how much is physically present. |
|
We'd better merge it to RTM (because it speeds up the analyzer at about 80 times) or disable it there. |
# Conflicts: # vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs
|
@dotnet-bot test this please |
KevinRansom
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good
|
@vasily-kirichenko can you resolve the conflicts pls. Kevin |
…ame-analyzer # Conflicts: # src/fsharp/TypeChecker.fs # src/fsharp/vs/service.fs # src/fsharp/vs/service.fsi
This reverts commit a2f791d. # Conflicts: # src/fsharp/TypeChecker.fs
…copes) by line number" This reverts commit c8edcc3. # Conflicts: # src/fsharp/vs/service.fs # src/fsharp/vs/service.fsi
|
@KevinRansom Could you please merge it? The analyzer is slow and it's still disabled in this PR (as in master) until we have a proper settings dialog to turn it on and off. Keep it up to date with master is cumbersome. |
| // PERFORMANCE: consider making this a struct. | ||
| [<System.Diagnostics.DebuggerDisplay("{idText}")>] | ||
| [<Sealed>] | ||
| [<Struct>] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm only now reviewing these changes. Please make sure I'm listed as a reviewer for all changes to the core compiler data structures, just to double check.
Do we know for sure this as a good idea? range is already a largish struct, and we had previously held off making Ident a struct too until we had some data
| member x.SetIsStructRecordOrUnion b = let x = x.Data in let flags = x.entity_flags in x.entity_flags <- EntityFlags(flags.IsPrefixDisplay, flags.IsModuleOrNamespace, flags.PreEstablishedHasDefaultConstructor, flags.HasSelfReferentialConstructor, b) | ||
|
|
||
| and [<RequireQualifiedAccess>] MaybeLazy<'T> = | ||
| | Strict of 'T |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good change, thanks. The name is a bit confusing since it might be confused with the Haskell terminology of maybe for option. Also in future please make sure we have /// comments for all new types, methods and properties for these core types.
| nlr: NonLocalValOrMemberRef } | ||
| member x.IsLocalRef = match box x.nlr with null -> true | _ -> false | ||
| member x.IsResolved = match box x.binding with null -> false | _ -> true | ||
| member x.IsLocalRef = obj.ReferenceEquals(x.nlr, null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please double check this change for performance or at least the generated IL
match box x with null -> ... | ... should, AFAIK, generate the best code, and should also allow optimization to brfalse conditiona sitching in the IL. There's no need to remove it from the compiler - if it's not generating good code we should fix our codegen.
| member vr.Deref = | ||
| match box vr.binding with | ||
| | null -> | ||
| if obj.ReferenceEquals(vr.binding, null) then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again there's no need for this change AFAIK. It's not a big problem either way, it's just that we should only make changes like this if they're needed
|
|
||
| // Can cpath2 be accessed given a right to access cpath1. That is, is cpath2 a nested type or namespace of cpath1. Note order of arguments. | ||
| let canAccessCompPathFrom (CompPath(scoref1,cpath1)) (CompPath(scoref2,cpath2)) = | ||
| let inline canAccessCompPathFrom (CompPath(scoref1,cpath1)) (CompPath(scoref2,cpath2)) = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't add inline to the compiler unless there's a very specific known and measured perf reason to do it, thanks
| let GetBestEnvForPos cursorPos = | ||
|
|
||
| let bestSoFar = ref None | ||
| let mutable bestSoFar = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good change
| let getChecker (document: Document) = document.Project.Solution.Workspace.Services.GetService<FSharpCheckerWorkspaceService>().Checker | ||
| let getPlidLength (plid: string list) = (plid |> List.sumBy String.length) + plid.Length | ||
| static let cache = ConditionalWeakTable<DocumentId, TextVersionHash * ImmutableArray<Diagnostic>>() | ||
| static let guard = new SemaphoreSlim(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's this lock for? Please add a big comment on this to explain why we need a lock here, thanks
|
@vasily-kirichenko Lots of good changes here - I'm only just reviewing them - I hadn't realized there were core changes in the compiler. I added some comments about the changes to the core compiler code @KevinRansom Probably best you make sure I review changes to the core compiler code. Just for a double check.
@vasily-kirichenko Yes, you're likely correct that Ident should be made a struct. However it can be hard to tell for objects that contain BTW I've found it quite hard to measure the pros/cons of these perf choices - the compiler perf scripts aren't yet accurate enough, though the memory tests are fairly accurate. Focusing on reducing memory by using structs can sometimes lead to reduced throughput through lots of data copying. |
* remove unnecessary filtering and deduplication * write lazy early return functions for SimplifyName analyzer * make the rest of nr lazy add cache * fix cache locking * do not try to resolve ctors for non-ctor Item * more lazyness to NameResolution * optimize GetBestEnvForPos by indexing nameres environments (scopes) by line number * look for best resolution env on previous line as well * Reformat ResolveObjectConstructorPrim * remove double hash table lookup in NotifyNameResolution * make CurrentSink non-optional * eliminate a list creation * renaming * optimize ValRef * small optimizations * make Ident a struct * add MaybeLazy * SimplifyNameDiagnosticAnalyzer should do semantic analysis, not syntax one * remove dead code * fix after merge * Revert "make CurrentSink non-optional" This reverts commit a2f791d. # Conflicts: # src/fsharp/TypeChecker.fs * Revert "optimize GetBestEnvForPos by indexing nameres environments (scopes) by line number" This reverts commit c8edcc3. # Conflicts: # src/fsharp/vs/service.fs # src/fsharp/vs/service.fsi * turn off SimplifyNameDiagnosticAnalyzer for now




Before
After
(it's the code fix recalculation for 1100 lines file from FSharp.Editing.Tests project from VFPT solution)
It looks like the diagnostics is called twice by VS, I think we should cache the last result in a
Dictionary<DocumentId, TextVersion hash * ImmutableArray<Diagnostic>>