Skip to content
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

[WIP] Added strong cache for ILModuleReaders #283

Merged
merged 1 commit into from
Mar 20, 2019
Merged

[WIP] Added strong cache for ILModuleReaders #283

merged 1 commit into from
Mar 20, 2019

Conversation

TIHan
Copy link
Contributor

@TIHan TIHan commented Nov 21, 2018

This adds a strong cache for ILModuleReaders instead of a weak cache. While we do a File.ReadAllBytes and it goes on the LOH, they will be long lived so it's appropriate. We do include a last write time to invalidate the cache just in case a user changes it; but they do not change frequently.

@TIHan TIHan changed the title Added strong cache for ILModuleReaders [WIP] Added strong cache for ILModuleReaders Nov 21, 2018
let reader = createReader ilGlobals file
(lastWriteTime, count + 1, reader)
else
(lastWriteTime, count, reader)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When do you remove?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about a MemoryCache with a 30 second expiration?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timed caches I guess can work, but I still feel a little uncomfortable with it.

@TIHan
Copy link
Contributor Author

TIHan commented Nov 21, 2018

@dsyme this isn't quite complete because now this is effectively a memory leak. We need to figure out when we can clear it out and have some sort of life-cycle for it.


[<Fact>]
let ``test reader cache actually caches``() =
for i = 1 to 1000 do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test looks great. Do you know the count before the cache is/was fixed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the actual count as it would be hard to test with a weak reference, but at least the test verifies that we actually do a strong cache.

@dsyme
Copy link
Contributor

dsyme commented Nov 22, 2018

@dsyme this isn't quite complete because now this is effectively a memory leak. We need to figure out when we can clear it out and have some sort of life-cycle for it.

Yup. But looking good :)

@dsyme dsyme mentioned this pull request Nov 23, 2018
@dsyme
Copy link
Contributor

dsyme commented Nov 23, 2018

@TIHan I resolved the merge conflict for you (I hope I didn't make a mistake)

@dsyme dsyme closed this Nov 26, 2018
@dsyme dsyme reopened this Nov 26, 2018
@blumu
Copy link

blumu commented Nov 28, 2018

So here is the result of my testing with a private build of FSharp.Data provided by @TIHan based on this fix:

  • The fixed might have helped a bit (VS felt snappy for the past 2-3 days). It's possible that the memory footprint was reduced but I cannot confirm for sure. My testing including changing code in our solution consuming Json type providers.
  • The original performance issue (slow VS typing over time #4718) that initially triggered this line of work, still reproes for me: typing slowdown, then freeze, then crash. Though, I was not touching the projects consuming provided types when this occurred. So the slow-down could still be due to other GC-related perf bugs filed by @davkean
  • I've shared a trace at https://developercommunity.visualstudio.com/content/problem/393151/typing-slowdown.html taken while VS was freezing, before it crashed. @TIHan looked at it and unfortunately
    did not find anything usefull in it.

@davkean
Copy link

davkean commented Nov 28, 2018

I also looked at the trace - the trace is too long (3 minutes), which meant that we threw out events. Traces need to be less than 30 seconds.

@blumu Can you turn up Diagnostic data to "full" in the Diagnostic & Feedback windows setting page? Your machine isn't sending any performance/watson data so cannot see any past crashes, UI or typing delays.

@davkean
Copy link

davkean commented Nov 28, 2018

Nevermind, I found some data through other means, just digging through it.

@davkean
Copy link

davkean commented Nov 28, 2018

@TIHan We don't need a trace to see GC's, if you look at the vs/perf/clrpausemsecs event in the session, it will show GC sizes/times. We will send an event for any GC over 500ms. I don't yet have results for the above session, but I looked at past sessions and GC time, in particular, Gen 2, is absolutely almost all the cause of your delays. The Gen2 + Large Object Heap are all huge, resulting in GC after GC after GC, all causing large delays of upwards of 2 seconds each.

When the results are in for the above session in a day, then @TIHan should be able to see if it's still GC that is causing the issue.

@dsyme
Copy link
Contributor

dsyme commented Mar 20, 2019

@TIHan We discussed using MemoryCache to allow resources to be reclaimed via a sliding window.

Unfortunately System.Runtime.Caching is not in .NET 4.5 nor .NET Standard 2.0, and TP design time (TPDTC) components are nearly always now compiled as one or both of those, to allow them to deploy into most active tooling.

So I will just accept this PR. Additionally I have added an off-by-default resource reclaimer that clears the reader table every minute when type providers are in active use. TO enable it set environment variable

FSHARP_TPREADER_AUTOCLEAR

to any non-empty value. It is likely the fixes we already have will be enough to reduce memory usage sufficiently

@dsyme dsyme merged commit 8cc9f9f into master Mar 20, 2019
@davkean
Copy link

davkean commented Mar 21, 2019

A cache without an invalidation policy is a memory leak. Visual Studio spans solutions loads, project loads, branch switches all of which could result this in cache growing unbounded.

@cartermp
Copy link
Contributor

The TPSDK should no longer support net45 if we cannot have a proper solution here.

@davkean
Copy link

davkean commented Mar 21, 2019

I don't think a sliding window is a great approach either, it should be tied to the life of something; project data, or TP themselves.

@blumu
Copy link

blumu commented Mar 21, 2019

I tested the fix of FSharp.Data (3.0.1) provided by @dsyme based on this version of FSTPSDK and I can confirm that the problem went away. See fsprojects/FSharp.Data#1249 for details

@davkean I am not sure if it's relevant, but for us the issue reproes systematically right after launching VS and calling "Find all references" for the first time. Which suggests that caching would help even within the scope of a single build. In other words, even if you were to invalidate the cache on every build that would still resolve the perf issue for us. I can see how this would break incremental builds though. So perhaps there is a better way to scope the cache as you suggested.

@dsyme
Copy link
Contributor

dsyme commented Mar 30, 2019

A cache without an invalidation policy is a memory leak. Visual Studio spans solutions loads, project loads, branch switches all of which could result this in cache growing unbounded.

Yes, we should clear here. For example we could implement an expiration policy by starting a background async to clear the cache under some policy. Switching to System.Runtime.MemoryCache, is not feasible as TP components really need to be netstandard2.0 realistically and it's not available there AFAICS.

I don't think a sliding window is a great approach either, it should be tied to the life of something; project data, or TP themselves.

The problem is we need to artificially extend the lifetime of these objects so they are shared between TP instances. We don't have any larger handle/scope for which to share them

@dsyme
Copy link
Contributor

dsyme commented Mar 30, 2019

@davkean I'll arrange for a better fix here. BTW I notice FSharp.Data itself has a bunch of caches too. It would be so good if we could truly load/unload components in isolation...

@dsyme
Copy link
Contributor

dsyme commented Apr 9, 2019

@davkean Please check out #295 to see if you think it meets your requirements/expectations as a tactical fix

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants