cosven
2021 年12 月 10 日 20:59
1
我在使用 lsp-find-definition 功能的时候,UI 总是会卡一下,快的话,感受不是很明显,慢的话会卡 1-2s。卡是指:我快捷键触发 lsp-find-definition 功能后,然后按上下左右,光标是不动的。
我看 lsp-mode 说自己是异步非阻塞调用?我理解无论 lsp 的后端多慢,UI 层都不会卡才对把。但我的实际体验是,我需要会等后端返回(或者等请求超时后),我才能进行其它 UI 操作。
这是我的配置问题么,还是说这种阻塞就是符合预期的?如果是我的配置问题,我应该如何定位呢?
应该是预期的吧,如果不卡住,你一直在那里点跳转,岂不是一直跳跳跳跳
cosven
2021 年12 月 11 日 01:52
3
我觉得 vscode 的处理方式比较理想:按下跳转后,后台在找,UI 层面不卡主。等后台找到函数定义了,UI 层就跳到那个文件。
可以先按照文档排查下卡在那里了:
而且 lsp 有很多华而不实的东西,我一般只用查找定义,其他的像 diagnose/lsp-ui 都关掉,再不行就试试 eglot,轻量很多。
2 个赞
cosven
2021 年12 月 12 日 02:48
6
按照这个文档 profile 了一下 ~ 我的操作方式是
M-x profiler-start
使用快捷键触发 lsp-find-definition 操作
期间疯狂按 ctrl-n 按钮(以判断 UI 是否能相应)
M-x profiler-stop
- command-execute 405 90%
- call-interactively 405 90%
- funcall-interactively 405 90%
- counsel-M-x 341 76%
- ivy-read 325 72%
- read-from-minibuffer 25 5%
- ivy--queue-exhibit 7 1%
- ivy--exhibit 7 1%
- ivy--update-minibuffer 6 1%
- ivy--format 4 0%
- mapcar 4 0%
counsel-M-x-transformer 4 0%
- ivy--filter 2 0%
- ivy--re-filter 2 0%
- cl-remove 2 0%
- apply 2 0%
- cl-delete 1 0%
#<compiled 0x1ff116397f3d> 1 0%
ivy--insert-minibuffer 1 0%
- timer-event-handler 2 0%
- apply 2 0%
- auto-revert-buffers 2 0%
mapcar 1 0%
- redisplay_internal (C function) 1 0%
eval 1 0%
- ivy--reset-state 4 0%
- all-completions 1 0%
counsel--M-x-externs-predicate 1 0%
- ivy--filter 1 0%
ivy--re-filter 1 0%
- ivy-thing-at-point 1 0%
- thing-at-point 1 0%
- thing-at-point-url-at-point 1 0%
- thing-at-point-bounds-of-url-at-point 1 0%
thing-at-point--bounds-of-well-formed-url 1 0%
- ivy-call 1 0%
- counsel-M-x-action 1 0%
- command-execute 1 0%
- call-interactively 1 0%
- funcall-interactively 1 0%
profiler-stop 1 0%
- counsel--M-x-externs 16 3%
- smex-detect-new-commands 6 1%
- mapatoms 5 1%
#<compiled 0x1ff11579932d> 5 1%
- lsp-find-definition 44 9%
- lsp-find-locations 44 9%
- lsp-request 41 9%
- accept-process-output 2 0%
- timer-event-handler 2 0%
- apply 2 0%
auto-revert-buffers 1 0%
- lsp--locations-to-xref-items 2 0%
- seq-map 2 0%
- apply 2 0%
- #<compiled 0x1ff11521f305> 2 0%
- mapcar 2 0%
- #<compiled 0x1ff115a49af1> 2 0%
- find-buffer-visiting 2 0%
- file-truename 2 0%
- file-truename 1 0%
- file-truename 1 0%
- file-truename 1 0%
- file-truename 1 0%
- file-truename 1 0%
file-truename 1 0%
- lsp-show-xrefs 1 0%
- xref--show-defs-buffer 1 0%
- xref-pop-to-location 1 0%
run-hooks 1 0%
- next-line 20 4%
- line-move 20 4%
- line-move-partial 19 4%
- pos-visible-in-window-p 2 0%
- eval 2 0%
flycheck-mode-line-status-text 2 0%
- posn-at-point 1 0%
file-remote-p 1 0%
+ ... 37 8%
+ timer-event-handler 3 0%
+ redisplay_internal (C function) 1 0%
但我觉得卡主,不一定是 CPU 处理不过来,可能是在等 IO…
cosven
2021 年12 月 12 日 03:27
7
根据这个 profile 结果,然后人肉看了下 lsp-mode 的部分代码。发现 lsp-find-definition 它本身就是个 blocking 的操作,它会调用 lsp-find-locations,lsp-find-locations 会调用 lsp-request 并等待它返回结果。
在 lsp-find-definition 的地方加了个 measure-time 的东西来打印时间,看到是这个函数确实会 block 很久。慢的时候需要 3s 左右,快的时候 0.x 秒。
(defmacro measure-time (&rest body)
"Measure the time it takes to evaluate BODY."
`(let ((time (current-time)))
,@body
(message "%.06f" (float-time (time-since time)))))
(cl-defun lsp-find-definition (&key display-action)
"Find definitions of the symbol under point."
(interactive)
(measure-time (lsp-find-locations "textDocument/definition" nil :display-action display-action)))
我怀疑它就是这样设计的… 有木有熟悉这块的老师来分析一下。
cosven
2021 年12 月 12 日 03:32
8
另外,我看到 lsp-mode 的代码里面确实有 xxx-async 的函数,它的 hover/highlight 等功能是调用这个函数,可能这些功能是非阻塞的。但 lsp-find-definition 是阻塞。
cireu
2021 年12 月 12 日 05:19
9
这样设计应该是为了保持一致性,免得按了跳转之后又去做其他事情了然后突然给你跳走了
其实可以按照snails的tick-id那样设计,如果异步返回结果id不对,就抛弃计算。
这个操作是阻塞式的从设计上讲是合理的,问题是在于为什么会隔很久返回。
1 个赞
cosven
2021 年12 月 13 日 03:00
12
我觉得“阻塞这个设计本身”不够健壮,不是最优的设计方案。因为它要依赖外部它控制不了的系统,并且 lsp spec 里面它也没规定这个请求就一定要在多久时间内返回。
我用它的 async 函数实现了一个简单的非阻塞版本,然后自己体验了一下,遇到了一个大家都能想到的问题:触发 find-definition 后,做了一些操作改变了 UI 布局,当 find-definition 处理完成后,它会突然改变我的 UI 布局,给我一个“惊喜” 。
所以类似这样的策略「其实可以按照snails的tick-id那样设计,如果异步返回结果id不对,就抛弃计算。」就很重要。或者是一些特定的操作能够主动的 cancel 这个请求,那也挺好的。
(defun lsp-find-definition-no-wait ()
(interactive)
(lsp-request-async
"textDocument/definition"
(append (lsp--text-document-position-params) nil)
(lambda
(loc)
(if (seq-empty-p loc)
(lsp--error "Not found for: %s" (or (thing-at-point 'symbol t) ""))
(lsp-show-xrefs (lsp--locations-to-xref-items loc) nil nil)))))
(global-set-key (kbd "C-c .") 'lsp-find-definition-no-wait)
但不得不说,如果 lsp server 反应快一点,用户体验基本是 OK 的。