Code-completion performance improvement via server-side filtering

I have changed SourceKit-LSP master branch to perform code-completion filtering on the server side and limit the number of completion items to 200 results by default. This should radically speed up completion for global results when large modules are imported like in the following example.

import Cocoa
func test() {
  <complete here> // used to take 3 seconds; now <100 ms on my machine
}

Note: the very first global completion after (re-)starting sourcekit-lsp populates our module completion cache, so make sure you do a second completion to see the performance improvement.

I verified that server-side filtering is working in VSCode, Sublime Text, and vim (with coc.nvim). If any editors do not support LSP's isIncomplete flag yet, it is possible to disable server-side filtering.

How to try it out

If you have seen slow completion in the past, please give it a try and let me know if you run into any issues with the new behaviour. You can report issues at bugs.swift.org with the SourceKit-LSP component, or mention them in this thread.

Option 1) Building SourceKit-LSP from Source

Server-side completion filtering is enabled by default on the master branch, so you can try it by following the instructions to build sourcekit-lsp from source.

Option 2) Toolchain development snapshot

I will update this thread when the change is available in a downloadable snapshot.

Update: now available in Trunk Development (master) snapshots starting with the August 4 snapshot (swift-DEVELOPMENT-SNAPSHOT-2020-08-04-a).

You can try it out using the instructions at getting started for a Swift.org toolchain.

Configuration

The new server-side filtering behaviour is on by default, but if you need to change or disable it there are a couple of ways.

  1. There are new command-line options for sourcekit-lsp
sourcekit-lsp \
  -completion-server-side-filtering=true \
  -completion-max-results=200
  1. Alternatively, you can pass initializationOptions to SourceKit-LSP during the initialize request:
"initializationOptions": {
    "completion": {
        "serverSideFiltering": true,
        "maxResults": 200
    }
}

Note: In the initial implementation, setting maxResults to unlimited (0 or nil) will have worse performance than if you disabled server-side filtering. We should be able to close that gap, but for now I don't recommend using large values for maxResults.

How does it work?

The speedup comes from limiting the number of results we return, saving unnecessary serialization. This affects both the internal serialization between sourcekitd and sourcekit-lsp (built-in binary format) and sourcekit-lsp's public api (json-rpc). For completions with tens of thousands of results, avoiding serializing several MB of JSON is a major savings, but even for smaller cases it can be a win.

In order to limit the number of results, we need the editor to re-query sourcekit-lsp for completions whenever the user types additional characters to filter the completions (we call this the "filter text"). In LSP, this is accomplished by setting the isIncomplete: true flag on results, and the editor can then use triggerKind == triggerFromIncompleteCompletions to tell sourcekit-lsp that it is in the re-filtering case.

When using server-side filtering, we keep track of a code-completion "session" for a given source file and completion location.

foo.barB|
    ^   ^
    |    ` completion location
     ` start of identifier

In the above example, the completion session will be for the location after "." at the start of the identifier, and the completion location (usually the cursor location) determines what the "filter text" for the completion will be, in this case "barB".

Once we open a session, any subsequent completion requests that have the same location and use triggerKind == triggerFromIncompleteCompletions will efficiently re-filter the existing completions. If the new completion location is not compatible with the session, we return an error rather than recompute completions so that editors can rely on triggerFromIncompleteCompletions always being fast. Currently, any other trigger kind (manual or trigger character like ".") will open a new completion session, although we could improve that in the future to reuse the session if no other changes have been made to the document.

The underlying filtering uses sourcekitd's codecomplete.open, codecomplete.update and codecomplete.close requests that have been around for a while.

Future improvements

There is still some low-hanging fruit to speed up completion in sourcekitd, since the codecomplete.open code path hasn't been as heavily optimized as the original codecomplete request.

  • Remove the experimental "grouped" completion support from sourcekitd's codecomplete.open code path. This code is not being used, and it slows down the implementation of sorting and filtering.
  • Migrate codecomplete.open to use the more efficient binary serialization format internally in sourcekitd to match what we do for the regular codecomplete request (this doesn't affect the json format in sourcekit-lsp).
  • Speed up repeated filtering: currently each "update" request re-filters from scratch, but in the common case where an update has fewer results than the previous one (going from "S" to "Str") we could do less work. Of course, since the filtering is fuzzy, we still need to account for low priority results getting higher priority in a subsequent update.
26 Likes

Unable to compile. I keep getting these errors on MacOS:

/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/usr/lib/swift/AppKit.swiftmodule/x86_64.swiftinterface:7:8: error: no such module '_SwiftAppKitOverlayShims'
import _SwiftAppKitOverlayShims
       ^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/usr/lib/swift/AppKit.swiftmodule/x86_64.swiftinterface:1:1: error: failed to build module 'AppKit' from its module interface; the compiler that produced it, 'Apple Swift version 5.2 (swiftlang-1103.8.25.8 clang-1103.0.30.20)', may have used features that aren't supported by this compiler, 'Apple Swift version 5.3-dev (LLVM d5c66e0774, Swift f6516662e8)'

I getting a lot of similar errors.

Why is it trying to use 'AppKit'?

Is this while building sourcekit-lsp itself, or while using it in a project?

the compiler that produced it, 'Apple Swift version 5.2 (swiftlang-1103.8.25.8 clang-1103.0.30.20)', may have used features that aren't supported by this compiler, 'Apple Swift version 5.3-dev (LLVM d5c66e0774, Swift f6516662e8)'

This looks like a problem mixing the open source compiler toolchain with something generated by the Xcode toolchain. I don't think this has anything to do with sourcekit-lsp.

Why is it trying to use 'AppKit'?

If this is building sourcekit-lsp, that's a good question. We don't have any direct dependency on it.

Are you perhaps trying to build a full toolchain? I see someone else ran into this:

I cloned the source kit-lsp repo. I then installed the Toolchains using the Xcode option since there is no other option for MacOS. I even put the new toolchain first in my PATH. However when I tried to build source kit-lsp. I got those errors. Is there no way in MacOS to isolate the swift environment from Xcode?

This is not specific to sourcekit-lsp, I see the same error building anything that transitively imports AppKit. In the case of sourcekit-lsp, our test support code imports XCTest, which imports AppKit.

It appears that using current toolchains with Xcode 11.* SDKs will not work. I'm not clear if this is a bug or expected behaviour.

I think you will need to use the Xcode 12 beta SDKs when building with the toolchain you have.

Is there no way in MacOS to isolate the swift environment from Xcode?

Not for modules that are in the SDK.

It sounds like this is expected due to changes in the overlays. You'll need Xcode 12 SDKs.

Still having problems. I have installed source kit-lsp on ubuntu 18.0.4. Everything compiled(BTW, SQLite3 is a pre-requisite) and I then tried to use it with VSCode. I created the VSCode plugin and installed it It now seems that none of the packages that were fetched into the .build directory are being referenced. Only symbols from my direct project are being referenced. I have cleaned and built the project many times. So basically there is no code completion at all. I’m not sure what to do. I am at the end of my ropes. Ant suggestions would be appreciated.

Thanks
Bruce

Did you build the package?

It now seems that none of the packages that were fetched into the .build directory are being referenced. Only symbols from my direct project are being referenced.

Are they just missing from completion, or are they also causing errors, missing from documentation on hover, etc?

I built the package. I used the master branch of source kit-lsp along with the following:

Using this command:
swift build -Xcxx -I/home/bruce/dev/swift/active-version/usr/lib/swift -Xcxx -I/home/bruce/dev/swift/active-version/usr/lib/swift/Block
Note: active-version is a symbolic link to 'swift-DEVELOPMENT-SNAPSHOT-2020-07-22-a-ubuntu18.04'

I got the following error at the end:

/usr/bin/ld.gold: error: cannot find -lncurses
clang-10: error: linker command failed with exit code 1 (use -v to see invocation)
:0: error: link command failed with exit code 1 (use -v to see invocation)
[651/653] Linking sourcekit-lsp

I also used 'swift-5.3-DEVELOPMENT-SNAPSHOT-2020-07-30-a-ubuntu18.04’ by changing the active-version
This compiled and linked ok.

I changed the Server path setting in VSCode to: /home/bruce/dev/github.com/sourcekit-lsp/.build/x86_64-unknown-linux-gnu/debug/sourcekit-lsp
I changed the Tool path setting in VSCode to: /home/bruce/dev/swift/active-version/usr/bin

I then cleaned my basic vapor hello project.
I then built the project.

When I open a source file I find that any source files main application do have hover but any dependencies do not. Code completion did not work on the dependencies. I never really tried it on the project source files.

Hope that helps.

P.S. Now I am getting an error with the ''swift-5.3-DEVELOPMENT-SNAPSHOT-2020-07-30-a-ubuntu18.04’ compile:

/usr/bin/ld.gold: error: cannot find -lncurses .build/checkouts/swift-llbuild/lib/llvm/Support/Unix/Process.inc:336: error: undefined reference to 'setupterm' .build/checkouts/swift-llbuild/lib/llvm/Support/Unix/Process.inc:354: error: undefined reference to 'tigetnum' .build/checkouts/swift-llbuild/lib/llvm/Support/Unix/Process.inc:358: error: undefined reference to 'set_curterm' .build/checkouts/swift-llbuild/lib/llvm/Support/Unix/Process.inc:359: error: undefined reference to 'del_curterm' clang-10: error: linker command failed with exit code 1 (use -v to see invocation) :0: error: link command failed with exit code 1 (use -v to see invocation) [651/653] Linking lib_SourceKitLSP.so

Does it work if you do

swift build --target sourcekit-lsp

Otherwise, this should fix it:

apt-get install libncurses5-dev

Okay, sounds like an issue computing the compiler arguments from swiftpm, rather than an issue with code-completion itself. If you've built sourcekit-lsp before, you might need to swift package update before building sourcekit-lsp. It's also possible there is an incompatibility between master swiftpm and 5.3 swiftpm, in which case building your project with a master toolchain instead of a 5.3 toolchain should fix it.

Hi Ben,

Well I have successfully built source kit-lsp for both the master toolchain and the 5.3 toolchain. Vapor runs fine under the swift 5.3 toolchain. However, I cannot build my vapor project under the master toolchain because of a bug in the master toolchain. Specifically,

Assertion failed: (ctx.Diags.hadAnyError()), function evaluate, file /Users/buildnode/jenkins/workspace/oss-swift-package-osx/swift/lib/Sema/TypeCheckType.cpp, line 3901.

Tanner (Vapor) has mentioned that the master branch is unstable and really can’t be used with Vapor at this time.

I tried to use the 5.3 Development branch but I got the same results. Project package dependencies are not being seen by sourcekit-lsp.

I will have to wait until your updates get into the 5.3 development branch.

Thanks for your help

Bruce

Also, It would be really nice if sourcekit-lsp fixes could also support earlier toolchains especially the currently released toolchain 5.2.4. Otherwise, we always have to wait for the next release to get any fixes/enhancements.

Only two months left, be patient. :laughing:

If you haven't already, could you please file a bug for this (bugs.swift.org)? This sounds like a compiler bug.

I tried to use the 5.3 Development branch but I got the same results. Project package dependencies are not being seen by sourcekit-lsp.

That's unexpected. Is this the same project as Importing modules from Vapor causing long delays - #3 by bmontegani ?

I will have to wait until your updates get into the 5.3 development branch.

Unfortunately, I suspect it is too late to take such a big and potentially destabilizing change in 5.3.

Also, It would be really nice if sourcekit-lsp fixes could also support earlier toolchains especially the currently released toolchain 5.2.4. Otherwise, we always have to wait for the next release to get any fixes/enhancements.

We've talked about creating a branch of sourcekit-lsp that is auto-merged from master sourcekit-lsp, but using the latest released toolchain swiftpm, covered by [SR-12491] Create a branch of sourcekit-lsp setup with the latest released Swift toolchain · Issue #536 · apple/sourcekit-lsp · GitHub I think this would be good to have, we just haven't had time to set up the infrastructure needed to keep a branch like this up to date.

Update: this change is now available in Trunk Development (master) snapshots starting with the August 4 snapshot (swift-DEVELOPMENT-SNAPSHOT-2020-08-04-a).

You can try it out using the instructions at getting started for a Swift.org toolchain.

I also updated the original post to match.

I was really happy that sourcekit-lsp shipped for the first time with Xcode 11.4. Unfortunately, it was completely unusable for me. It blocked my editor. The sourcekit-lsp in Xcode 12 works perfectly for me.

Glad to hear it's working better for you in Xcode 12! Just to clarify: this improvement for code-completion server-side filtering did not make it in time for that release.