lsp-mode + gopls high cpu usage when import package

Created on 18 Jan 2020  路  19Comments  路  Source: emacs-lsp/lsp-mode

Describe the bug
I've been configuring lsp-mode/gopls + lsp-ui + company-lsp + helm-lsp + flycheck for a while, I use the latest version for everything. Mostly with auto-configure, also copied some lsp-mode configuration from doom. But when I'm editing the import list of code to import new packages, cpu usage upsurge and my fan start roaring. The high cpu usage even lasts for tens seconds after I stopped typing. Conversely, I tried VSCode (default setting) with same code snippet, editing import list also caused cpu usage rising, but in a very acceptable range, and got down immediately after I stop typing.

Here is a cpu load chart which can give you a perception of the difference
image

To Reproduce
Any code call import, like this:

package main

import (
    "flag"
    "fmt"
    "os"
    "path/filepath"
    "time"

    "k8s.io/apimachinery/pkg/api/errors"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/clientcmd"
)

func main() {

}

As you typing the import statement character by character, the cpu will rise up.

Expected behavior
The performance should be at a comparable level with VSCode, as they are using same language server.

Which Language Server did you use
golang.org/x/tools/gopls 0.2.2
golang.org/x/tools/[email protected] h1:ujGisyytgY1VGcmd66wIJ9+wVAfmodXj6daHM43HRXk=

OS
macos 10.15.1

Error callstack
with gopls debug, I compared the rpc requests between vscode and lsp-mode. Here I only paste the ones with significant latency difference as well as the ones which only appear at one side.

| Inbound RCP | VSCode | lsp-mode |
| ------------| ------ | -------- |
| $/cancelRequest | 72 traces Latency 384.06ms | 226 traces Latency 26.95s |
| textDocument/codeAction | 66 traces Latency 325.56ms | 124 traces Latency 21.82s |
| textDocument/completion | 6 traces Latency 754.60ms | 4 traces Latency 29.08s |
| extDocument/didChange | 148 traces Latency 194.74ms | 178 traces Latency 18.11s |
| textDocument/documentLink | 41 traces Latency 280.50ms | 164 traces Latency 19.61s |
| textDocument/documentSymbol | 49 traces Latency 486.40ms| nil |
| textDocument/foldingRange | 76 traces Latency 127.96ms | nil |
| textDocument/hover | nil | 150 traces Latency 21.22s |
| textDocument/signatureHelp | nil | 87 traces Latency 33.42s |

| Outbound RCP | VSCode | lsp-mode |
| ------------| ------ | -------- |
| client/registerCapability | 1 traces Latency 7.30ms | nil |
| textDocument/publishDiagnostics | nil | 97 traces Latency 22.49ms |

Params of initialize request of each : https://gist.github.com/zozowell/30bf9c00d111cd5249dd3d2a7bf41e03

Most helpful comment

It sounds like the high CPU usage is satisfactorily fixed on gopls master, so I think that resolves this issue. I don't think it is reasonable to expect two different editors to display near identical CPU usage.

Also, typing directly in import statements is a pathological gopls case, and normally is not needed (e.g. use lsp-organize-imports to add/remove imports automatically, or unimported completions which let you complete packages not currently imported).

auto-completion doesn't work any more for methods of imported package

That sounds like a separate issue. Can you open a gopls issue under the Go issue tracker with steps to reproduce?

All 19 comments

@muirdm , it may need your insights here, much appreciation if you could give this a look.

Can you build gopls on master and give it a try?

@muirdm Sure thing, will do. But is this a known issue of gopls? the question is, lsp-mode and vscode now are using same gopls, but have different performance. Thanks for the prompt response.

I couldn't reproduce your issue in Emacs on gopls 0.2.2 or master. CPU spike was a bit worse in 0.2.2, but it didn't persist long after I stopped typing.

Changes to imports trigger a (sometimes really expensive) "go list" call to refetch metadata. gopls is supposed to avoid letting these pile up and melt your CPU. There have been a lot of metadata related improvements since 0.2.2.

I don't know why it varies so much between VSCode and Emacs. Maybe VSCode batches up some edits where Emacs always sends each character one at at time.

@muirdm , I build with master, and found it's exact the one I installed now. The problem is still here.

golang.org/x/tools/[email protected] h1:ujGisyytgY1VGcmd66wIJ9+

I now remove all the configuration left only following, but no help at all.

  (setq lsp-idle-delay 1.0)
  (setq lsp-log-io nil)
  (setq company-idle-delay 1.0)
  (setq company-minimum-prefix-length 3)
  (setq company-lsp-cache-candidates 'auto))

Maybe VSCode batches up some edits where Emacs always sends each character one at at time.

May you please suggest any setting can make emacs does same? Or may you possible share your config to let me try if it works all well at your side.

Thanks!

golang.org/x/tools/[email protected]

That looks like you are still on 0.2.2. My gopls version shows:

golang.org/x/tools/gopls master
    golang.org/x/tools/gopls@(devel)

To install master you should checkout the tools repo on master, cd gopls; go install.

Can you give exact steps to reproduce? Where/what are you typing in your sample file? What process is using a lot of CPU? What do you see in *lsp-log* buffer? There should be messages for each "go list" gopls runs.

@muirdm, sorry my confusion, now I'm using the latest master to replace v02.2, and the cpu usage level does drop down dramatically. However, that's an equal drop in both lsp-mode and vscode, and lsp-mode is still obviously higher than vscode.

The step to reproduce:

  1. create a source file hello.go
package main

import (
)

func main() string {
    return "Hello"
}
  1. go init mod example.com/hello
  2. type follow packages names in the import function
import (
    "flag"
    "fmt"
    "os"
    "path/filepath"
    "time"

    "k8s.io/apimachinery/pkg/api/errors"
)

I see the cpu usage rise along the typing. Additionally, I see another weird error message with new gopls.
image

Emacs is, in general, not very CPU efficient, so it wouldn't surprise me if the discrepancy between Emacs and VSCode is just due to VSCode being more efficient. In my testing, while constantly typing Emacs used 30-40% CPU and VSCode used only 5%.

I profiled Emacs and most of the time didn't seem to be in lsp related stuff. Can you take a CPU profile in Emacs while typing?

@zozowell Which version of Emacs are you using? I believe 27+ has better performance with --with-jansson option. Check if there is --with-json in system-configuration-options.

@muirdm , the high cpu is mostly contributed by gopls driven by emacs, it could reach 40%+. emacs itself was also high, but at a comparable level to vscode main process. Refer to the rpc call list in my first post, you may see that, emacs obviously initiated more requests to gopls, with higher latency in comparison. That's making me feel, there're probably some aggressive behaviors at lsp-mode side which caused this.

Here is cpu profiling:

- redisplay_internal (C function)                                 376  32%
 - jit-lock-function                                              368  31%
  - jit-lock-fontify-now                                          366  31%
   - jit-lock--run-functions                                      363  30%
    - run-hook-wrapped                                            361  30%
     - #<compiled 0x1fea8d60ac45>                                 361  30%
      - font-lock-fontify-region                                  360  30%
       - font-lock-default-fontify-region                         360  30%
        - font-lock-fontify-keywords-region                       345  29%
         - go--match-decl                                         230  19%
          - go--containing-decl                                   183  15%
             syntax-ppss                                          116   9%
           - go-goto-opening-parenthesis                           51   4%
              syntax-ppss                                          50   4%
             #<compiled 0x1fea8bb725a5>                             1   0%
            syntax-ppss                                            22   1%
            #<compiled 0x1fea8bb72505>                              1   0%
         + go--match-param-start                                   43   3%
         + eval                                                    11   0%
           go--match-ident-type-pair                                7   0%
         + go--match-ident-colon                                    6   0%
         + go--fontify-param                                        6   0%
           go--match-type-alias                                     3   0%
           go--match-type-switch-case                               1   0%
        + font-lock-fontify-syntactically-region                    8   0%
 + tty-color-desc                                                   5   0%
 + #<compiled 0x4186b193>                                           3   0%
- command-execute                                                 301  25%
 - call-interactively                                             300  25%
  - funcall-interactively                                         300  25%
   - self-insert-command                                          147  12%
    + sp--post-self-insert-hook-handler                            90   7%
    - lsp-on-change                                                49   4%
     - mapc                                                        49   4%
      + #<compiled 0x1fea8d60271d>                                 49   4%
      go--reset-dangling-cache-before-change                        2   0%
      undo-auto-amalgamate                                          1   0%
    + lsp-before-change                                             1   0%
   + helm-M-x                                                     134  11%
   + newline                                                       15   1%
   + delete-backward-char                                           2   0%
- timer-event-handler                                             238  20%
 - apply                                                          237  20%
  - company-idle-begin                                            139  11%
   + company-post-command                                          93   7%
   + company-auto-begin                                            46   3%
  + lsp--on-idle                                                   44   3%
  + #<compiled 0x1fea8c800809>                                     37   3%
  + #<compiled 0x41853fc3>                                          9   0%
    #<compiled 0x4183345f>                                          5   0%
  + hl-paren-highlight                                              2   0%
  + treemacs--follow                                                1   0%
   cancel-timer-internal                                            1   0%
+ ...                                                              77   6%
+ lsp-ui-doc--make-request                                         76   6%
+ #<compiled 0x1fea8ce52435>                                       41   3%
+ company-post-command                                             39   3%
- lsp--post-command                                                22   1%
 - lsp--cleanup-highlights-if-needed                               22   1%
  - lsp-cancel-request-by-token                                    20   1%
   - lsp--cancel-request                                           20   1%
    - mapcar                                                       20   1%
     + #<compiled 0x1fea8ce0f121>                                  20   1%
  + lsp--point-on-highlight?                                        1   0%
    lsp--remove-overlays                                            1   0%
  global-hl-line-maybe-unhighlight                                  1   0%
+ undo-auto--add-boundary                                           1   0%
  jit-lock--antiblink-post-command                                  1   0%

@seagle0128 , thanks for the suggestion, but I'm already using 28.0.50 which is compiled with --with-json.

BTW, I found the gopls for emacs doc here : https://github.com/golang/tools/blob/master/gopls/doc/emacs.md doesn't work at all, with this setting, neither company autocomplete nor flycheck works. Not sure if it is out of date or it's my setting problem. I made it worked with lots of twists, but it would be highly appreciated if there is an official recommended configuration.

Emacs sends hover/signature requests all the time where VSCode seems to have some smarts to not send them all the time, so that could be the difference. You can try disabling signatureHelp and hover in lsp-mode.el and compare again.

I found the gopls for emacs doc here : https://github.com/golang/tools/blob/master/gopls/doc/emacs.md doesn't work at all

Please file a separate issue under the Go issue tracker with details on what is wrong with the sample config (or open a PR yourself if you can fix it). Emacs config is too complex and opinionated to have an "official" config. That config is just meant to be a starting point (but it is supposed to work, regardless).

Emacs sends hover/signature requests all the time where VSCode seems to have some smarts to not send them all the time, so that could be the difference. You can try disabling signatureHelp and hover in lsp-mode.el and compare again.

This is no longer the case - we now work like vscode.

Every character I type in an import in Emacs causes a "documentHighlight" and "hover" request, which are both relatively expensive (14ms and 76ms, respectively).

Every character I type in VSCode I get textDocument/foldingRange, textDocument/codeLens, textDocument/documentSymbol, and textDocument/documentLink which all took 0ms.

Based on that, it seems like "hover" and "documentHighlight" are the difference.

Every character I type in an import in Emacs causes a "documentHighlight" and "hover" request, which are both relatively expensive (14ms and 76ms, respectively).

Maybe our idle delay is a bit aggressive - 0.2.

@zozowell can you test after increasing lsp-idle-delay ?

@yyoncho I tested with (setq lsp-idle-delay 1.0) against

  • lsp-mode-20200116.1037
  • golang.org/x/tools/gopls 0.2.2 (which I saw problem at the beginning)

Unfortunately it doesn't help. Checked lsp-log, gopls was doing lot of dependencies listing action, log were filled up with package description.

image

Then i installed gopls master latest.

  • golang.org/x/tools/gopls master

CPU usage gets much better, however, the auto-completion doesn't work any more for methods of imported package. It delayed for a long while (30 minutes?) before gopls finally recognized those packages, and start completing.

Here are all lsp-mode related config of mine, appreciate very much if anyone can help giving it a review. https://gist.github.com/zozowell/c9f6f2d2924b36525577da1e5f8acc93

It sounds like the high CPU usage is satisfactorily fixed on gopls master, so I think that resolves this issue. I don't think it is reasonable to expect two different editors to display near identical CPU usage.

Also, typing directly in import statements is a pathological gopls case, and normally is not needed (e.g. use lsp-organize-imports to add/remove imports automatically, or unimported completions which let you complete packages not currently imported).

auto-completion doesn't work any more for methods of imported package

That sounds like a separate issue. Can you open a gopls issue under the Go issue tracker with steps to reproduce?

Thanks @muirdm . Agree to close this one. As a summary, although I still can't understand what's the root cause of this issue (feel it's the aggressive rpc calls to gopls, but don't know how to control it), things helped improving performance most are :

  • Use gopls master latest, rather than the latest stable 0.2.2, significantly reduced CPU usage. See that 0.3.0 is coming soon.
  • Use Emacs 28 compiled with --with-json is another thing might help.

BTW, I found the gopls for emacs doc here : https://github.com/golang/tools/blob/master/gopls/doc/emacs.md doesn't work at all, with this setting, neither company autocomplete nor flycheck works. Not sure if it is out of date or it's my setting problem. I made it worked with lots of twists, but it would be highly appreciated if there is an official recommended configuration.

I got it to work with completion and flycheck. I used this article for setup and I used C-c ! v to check my flycheck setup (very useful and informative but the changes you make with it aren't persistent), which told me it was using lsp for checking. I changed that to golint by adding a flycheck line to the custom lsp-go-install-save-hooks from the article:

(defun lsp-go-install-save-hooks ()
  (add-hook 'before-save-hook #'lsp-format-buffer t t)
  (add-hook 'before-save-hook #'lsp-organize-imports t t)
  (setq flycheck-checker 'go-golint))
Was this page helpful?
0 / 5 - 0 ratings