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

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
@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:
hello.gopackage main
import (
)
func main() string {
return "Hello"
}
go init mod example.com/helloimport (
"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.

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
Unfortunately it doesn't help. Checked lsp-log, gopls was doing lot of dependencies listing action, log were filled up with package description.

Then i installed gopls master latest.
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 :
--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))
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).
That sounds like a separate issue. Can you open a gopls issue under the Go issue tracker with steps to reproduce?