-
-
Notifications
You must be signed in to change notification settings - Fork 369
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
Excessive memory usage in monorepos with dozens/hundreds of cabal packages or modules #2151
Comments
@jneira thanks for the summary. I don't have time to look into this but can share a few pointers:
Good luck and happy hacking! |
Many thanks for your insights, for completeness the pr about enabling the hls becnhmark example is #1420 |
I'm having access to one private monorepo with ~250 "packages" and more than 1000 modules which exhibits huge memory usage (we upgraded dev machines to 32 GB of RAM in order to use HLS). I still have good contact with another company with a similar monorepo. Both are using bazel as the build system and the flags are generated from bazel. Note that there is one "component" (i.e. on I can run every benchmarking you may suggest on this repo. |
The Haskell codebase at Mercury is 232kloc in a single A large number of packages probably contributes to slowdown, but even with a single package target, HLS is unusably slow for most of our developers, even those with 32GB of RAM. |
I think some plugins contribute to the slowness, did they try to only use certain (base) plugins? |
How do I disable plugins ? I.e. starting with the minimum and progressively increasing them? Is there a runtime option or should I just rebuild HLS without them? |
I am not sure whether there exists a run-time option (there definitely should be), I built from source using cabal flags, and modified the constraints in |
in how many components (libs, test suites, benchmarks, execs) are distributed those modules? |
To disable plugins at runtime you could use the lsp client provided json config, i will try to post a config example which disables all plugins. |
Try this branch - #2060 |
I cannot comment for the mercury codebase, but for my codebase there is no "composent". I'm using a Is reducing this output size AND limiting how they are different a possible way to reduce memory usage / invalidation? |
This is a worst-case scenario. Best case would be a custom cradle that returns a universal set of flags and then lists all the module paths. |
We have a single component that we want to load, |
ok, if you run default config
|
CircuitHub uses HLS (well, I do, at least!) over its 1760 modules split over 50 |
Some initial observations on the same codebase @parsonsmatt is talking about. We're working on improving HLS's performance on this codebase. The (private) codebase has about 1401 modules in one single package. Configuration:
HLS uses for stack the Here is
Meanwhile, if I allow HLS to proceed with loading all the .hie files, then we have 6GB total memory use with a maximum residency of 2GB (and 251GB total allocations).
That's this many .hie files:
It's hardly surprising, as the So my initial impressions are:
I'll follow up with numbers when plugins are enabled. |
Here are the same numbers with default plugins enabled without any flags passed. The maximum residency doubles to 4GB with total memory use jumping to an almost double 9.7GB.
Therefore the summary is: the initial loading has very high memory use, the loading of the .hie files has very high memory use, and the plugins have very high memory use. As a small point of interest, the memory use before we consult the cradle in ghcide leads us already to 4GB maximum residency and 1GB total use.
At this point we have some easy areas to investigate. I'm likely to proceed with heap/allocation profiling tools from here to identify in each stage if there are any low-hanging fruit. |
@chrisdone wow, impressive analysis, many thanks
That seems to be a good path to get an improvement without too much changes. Ping @wz1000 as they introduced hiedb, which seems the correct tool to accomplish it (or it will be involved for sure)
That hurts, in that area we would need to analyze plugins separately, to identify what memory usage is shared for all of them and what make they increase it. For example is expected that the eval plugin will take much more than others, as it spans new ghc sessions instead reuse the created by ghcide. |
Awesome! I am happy to help, ping me on #haskell-language-server at any time.
Politely WTF?
#1946 might be relevant
Keep in mind that HLS type checks the entire codebase at startup by default. You probably want to disable this for your 1000s files codebase with
Sadly ghcide does not have garbage collection of build results. |
If you plan to do further analysis, I suggest that you use the #2060 branch |
How much TH is used in this codebase? |
We have 2 issues about garbage collector:
|
The codebase heavily uses TH and simple |
|
You may want to print the
It might be worth parsing some of its fields lazily, delaying allocations until actually needed. But that would probably require representational changes in GHC. A simpler option would be to void some of those fields immediately after parsing, or replace them with a lazy thunk to reparse. In some cases, they could be replaced by an end product, e.g. replace the full source by its hash. |
Thanks @pepeiborra. We also noted that there isn't one specific file that's humongous causing excessive allocations, if you look at the log, the allocations steadily grow with each I did more narrowing and found that making the HIE files lazy didn't save anything substantial (in this case). Indeed, despite not actually loading the HIE files and deferring them later via a "LazyHie" type, the memory growth was the same. So for loading from disk, the HIE files isn't actually causing problems. In fact, the culprit appears to be generation of hi files, here: - r <- loadInterface (hscEnv session) ms sourceModified linkableType (regenerateHiFile session f ms)
+ r <- loadInterface (hscEnv session) ms sourceModified linkableType (\_ -> pure ([] ,Nothing)) Above, replacing the
vs
(6.3GB to 2GB total memory use) We don't think that it should be regenerating these files. If you have any insights into this, please let us know! @qrilka has some evidence that the hashes for modules are instable, which may be a culprit. He's going to look more into this. |
Interesting. I would look at the recompilation logic and verify that it's working as expected. TH makes a difference here: haskell-language-server/ghcide/src/Development/IDE/Core/Compile.hs Lines 907 to 924 in ec53fcb
|
Our bench harness runs: _ <- try (removeDirectoryRecursive ".stack-work") :: IO (Either IOException ())
_ <- try (removeDirectoryRecursive (home ++ "/.cache/ghcide/")) :: IO (Either IOException ()) In order to clear the cache. Are there any other cache directories I should be aware of? I'm trying not to hit recompilation case at all and just do a cold run. (Our clients aren't able to finish the first cold run, thereby making any recompilation logic a secondary concern.) |
It looks like "Impure plugin forced recompilation" is the reason regeneration gets triggered in the project we are looking into. |
A cold run is expensive and should be avoided - we haven't done any work to optimise this scenario. For Sigma, I manually populate the ghcide cache by fetching all the hi and hie files from the cloud build system. |
that probably refers to a GHC plugin. Do you use any of those? |
I don't see anything other than what's shipped with GHC itself. I the project cabal file I see
and tracing shows empty properties |
|
I have implemented a form of garbage collection in #2263. Does it help at all? |
It turns out that the Tactics HLS plugin installs a GHC plugin: haskell-language-server/plugins/hls-tactics-plugin/src/Wingman/StaticPlugin.hs Lines 17 to 34 in ec53fcb
Apologies, I wasn't aware of this. |
Oh, yes, we've found this too, sorry for not sharing this |
@qrilka @chrisdone hi, not sure if you already had the chance of check all the perf improvements included in hls 1.5.0, could you give a try if that is not the case? thanks! |
sorry but we would need some feedback to confirm this continue being a problem in your repos: @qrilka @parsonsmatt @guibou, thanks! |
I don't have enough information on this currently but probably @parsonsmatt could give an answer. |
I did a small session to experiment. 10 minutes of opening files, moving through different files, writing a few functions (and generating errors), applying code actions, moving back to files which are depending on the one I modified, running some code eval, RAM usage of HLS is ~10GiB. (Actually, that's 9GiB in one HLS, and 1 GiB in another, I have no idea why my session generated two instances of HLS...) Most of the time I see a bit more after a longer editing session. Sometimes, the memory usage skyrocket. Yesterday I had to kill HLS because it was eating all of my ram and even more, entries in the log:
That's yesterday log when I was doing some serious Haskell work
So looks like around ~11 GiB. I'm surprised by the difference between live bytes and heap. Note: In this code I'm using a commit from HLS from a few days ago, so it is not 1.6.1, but it is close. So in summary:
Sorry, that's not a precise answer. I'll soon switch our work codebase to GHC 9.2, most of the work is done, we are still blocked on a GHC regression related to doctest AND on HLS eval plugin. Once this will be done, I'll run my daily dev using HLS with the GHC 9.2 info table profiling / event log. Hopefully I'll be able to gather details about the origin of the allocations. |
@guibou I fixed some issues on the GHC end which will end up in 9.2.2. Otherwise heap size being 2x of live bytes is normal with the copying collector - see https://well-typed.com/blog/2021/03/memory-return/ |
@mpickering I'm really looking forward for GHC 9.2.2 ;) I've had another look at your article. I'm surprised by this assertion that
|
Really wish I could second this as completed but HLS is still gobbling up all of my laptop's RAM (32gb) and then dying because it needs more when trying to use it on my work codebase. As of now (+ the "load serialized core on boot") it is still unusable for those of us with <64gb of RAM (those with 64GB of ram report it as "slow but usable") |
sorry I closed it cause there were no feedback for a while and I was not sure if it was actionable anymore, gonna open it again |
FWIW, I’ve had good experience with using hiedb. |
I'm not sure that having a generic ticket for "big projects use lots of memory" is actionable. Investigating memory usage and finding specific problems is useful, like #2962. |
The text was updated successfully, but these errors were encountered: