Making haskell-language-server loading more reponsive and smooth

In larger projects, HLS often loads slowly. One of the main obstacles is that we are loading GHC options for files in strict sequential order.

I’ve attempted to improve this situation by making two changes.
Thanks to @fendor 's multiple home units support and the his issue Initial load of multiple home units is slow with lazy component loading · Issue #4381 · haskell/haskell-language-server · GitHub, which provided some directions.

  1. If you have multiple home units support, load as many pending files as possible at once.
  2. Refactor the loader to reader and writer over cache. This way, the long-loading file won’t block already loaded files, making HLS more responsive and smooth.

These changes mostly affect Cabal projects, but other types of projects might benefit from the second change as well.
The PR can be found in Decouple the session loader into reader and writer over the cache by soulomoon · Pull Request #4445 · haskell/haskell-language-server · GitHub.
I’m currently dogfooding this and getting positive results. Testing and feedback are welcome if you’re interested. This PR needs more feedback from Haskell developers to help polish it further. :heart: :heart: :heart:

35 Likes

What a wonderfull news! Thanks for the hard work

5 Likes

For anybody looking for an easy way to try this out, you can use this ghcup command to build HLS with the pull request applied as a patch.

ghcup compile hls \
  -g master \
  --patch https://patch-diff.githubusercontent.com/raw/haskell/haskell-language-server/pull/4445.patch \
  --ghc <VERSION>

Alternatively, you can grab the latest commit hash from the pull request, or you can track the fork it comes from. I did it this way so that I can run the same command later to automatically get the current master with the most recent changes from the PR (which might not always apply cleanly or build successfully, but whatever).

Edit: The HLS produced by the above command seemed to take even longer to load than without the patch. I am now trying the following command to build instead, to make sure I’m using exactly what @soulomoon is working with.

ghcup compile hls \
  -r https://github.com/soulomoon/haskell-language-server.git \
  -g batch-load-multi-read \
  --ghc <VERSION>

Edit 2: I think either command works. I just hadn’t been using "sessionLoading": "multiComponent", as I didn’t realize it was no longer the default in HLS. I’m also not 100% certain that I observed a slowdown in the first place; maybe it just seemed slower because I was expecting it to be faster.

7 Likes

Thanks for the feedback, yes, "sessionLoading": "multiComponent" have to be enabled successfully for the first change(batch load) to take effect and observe faster load time. The second change is more about responsiveness while we are in middle of the loading proccess. Also we will fallback to non-batch load from batch load if we are encounter error in the batch load mode in order to distribute the errors to the correct failling files.

Since the change is quite subtle.
I made a video to showcase the responsiveness improvement. Youtube video

In case this confuses anyone else, the setting is called “multipleComponents”, but I think “multiComponent” was the original name and might have been preserved for backwards compatibility.

The problem really is that it isn’t documented. Although hopefully it’ll become the default soon anyway.

2 Likes