Hi everyone, welcome to the October edition of the Visual Studio Code Java update! In this post, we are going to take a deep dive of the recent performance improvement on auto-completion.
Performance Improvement – Faster Code Completion
With the recent 1.0 release of the Java Language Server, we have made substantial improvement on the performance of auto-completion. The chart below compares the code completion response time between recent versions. For common scenarios such as completing types and constructor names, the code completion performance is improved significantly compared to previous versions (v0.80, v0.81 and v.0.82)
Overview of Improvements
The completion engine consists of three phases:
- Phase 1 (P1) – Searching the indexer to find proposals
- Phase 2 (P2) – Converting proposals into completion items
- Phase 3 (P3) – Calculating code snippet proposals.
Based on our analysis, we found that there was room for improvement in all three phases. The following table shows the improvements we have made in the past versions. We will talk more about the details of those changes in the next section.
0.80.0 | 0.81.0 | 0.82.0 | 1.0.0 | |
Reduce I/O on Windows (P2) | N/A | ✅ | ✅ | ✅ |
Optimize for constants / default values (P2) | N/A | ✅ | ✅ | ✅ |
Delay resolving generic snippets (P3) | N/A | N/A | ✅ | ✅ |
Optimize for anonymous constructors (P2) | N/A | N/A | N/A | ✅ |
JDT Search Engine – Optimize unit.complete() (P1) | N/A | N/A | N/A | ✅ |
JDT Search Engine – Improve I/O of indexing files (P1) | N/A | N/A | N/A | ✅ |
Defer TextEdit calculation (P2) | N/A | N/A | Planned |
Key Changes in Recent Releases
Version 0.81.0 – Reduce I/O on Windows. #1831
In past benchmarks, we found a big proportion of time cost was to calculate URI of files. It also explained our observation that completion performance was relatively worse on the Windows platform because of platform-specific filesystem related implementation in JVM. By removing unnecessary calculation of URIs, we improved the performance especially on the Windows platform.
Version 0.81.0 – Optimize for constants/default values. #1835
When we complete a constant field (e.g. Constants.*), the completion popup will show the suggested field names as well as their constant values (e.g. Bit1 : int = 1) in the choice list. Our profiling found this is extremely slow when the class contains a large of constant field members. This is because we calculate the field value from AST Tree, which is expensive when operating on a large file.
To optimize it, we decide to defer resolving the constant value. The completion will simplify the suggestion label and only show the field name (e.g. Bit1 : int). And when you hover over this completion item for Javadoc, then display its constant value in the Javadoc section.
Here is a benchmark comparison of field completion on a class with 1400+ lines and 150+ constant fields.
Version | Average response time |
0.80.0 | 1429ms |
0.81.0 | 72ms |
Version 0.82.0 – Delay resolving generic snippets. #1838
There are two types of Snippets:
- Generic snippets (e.g. foreach, fori, ifelse, etc.)
- Type definition snippets (e.g. class, interface, etc.)
For generic snippets, it evaluates template patterns with given context before constructing the `TextEdit` of a completion item. Evaluation can be expensive. Now we defer the evaluation to the resolving stage. When a completion snippet item is constructed, template patterns are filled in as a placeholder. Actual values are evaluated in the resolving stage which doesn’t block completion items from showing. It’s also an experiment on how far “delay resolving TextEdit” can potentially improve the performance, and in most cases, it should work well.
Version 0.82.0 – Optimize for anonymous constructors. #1836
When we complete for new Runnable, the expected output should be:
Runnable() {}
It is made up of two parts.
- the name Runnable
- An empty body () {\n\t\n}
During performance profiling, we found CodeFormatUtil.format was costing a lot of time.
To have a correct indentation and line delimiters, they are formatted with current preferences. Formatting is expensive, and the same content (empty body) was repeatedly formatted for all items (sometimes up to thousands). To improve it, we format the empty body once and reuse it among all items.
Version 1.0.0 – Speed up the search performance of code completion.
There are two changes to optimize index search performance.
Our performance profiling shows that 97% of the CPU time of the index query jobs is spent on I/O for loading index content from disk. This is because the indexing mechanism we use tends to save memory and uses very little cache in the search engine. Almost every query must reload the index content from the disk. One straightforward optimization is to reduce the frequency of I/O.
The Java indexer consists of multiple hashtables, each used to record a certain type of code sections, such as type declarations, method declarations, references, method references, etc. A typical query job reads one or more hashtables from the index and then joins these index entries into the target result.
When we complete for the type/constructor name (e.g Str, new Str), the index query job reads two hashtables, one is the typeDecl table to find the matched type names, the other is the documentName table to find the class file path that declares the corresponding types. Since our purpose is just to complete the type name and auto insert its package import, the typeDecl table is sufficient for our requirement, and the class file path is not necessary. Our optimization is to read the typeDecl index table only, and it turns out reading one less index table can save a lot of I/O costs.
- Optimize the index reading. #574464
It comes from a community developer’s contribution to the upstream JDT project. The Java index uses UTF-8 to encode the index characters. When loading the indexes, we will decode them back. Since most index characters are just ASCII characters, we optimized the decoding method to make it faster to read ASCII.
Future Plans
The improvements we listed above have made the auto completion a lot faster, but we are not done. In the future, performance continues to be our top focus and we will continue to optimize the auto-completion performance. Here are some items we have planned in the next few months.
- Lazy Resolve TextEdit
Since most language clients don’t support lazy resolve text edit for the completion items, the Java language server must calculate the text edits for all completion items in the completion response. This is the cause of most expensive calculations. We’re collaborating with the client authors to explore the support for lazy resolving text edit.
- More Efficient Indexer
Current index data is insufficient for some code completion scenarios such as constructor. For example, the constructor completion needs to know whether the class has generic type arguments and decide whether to add a diamond <> to the constructor reference. The constructor index table hasn’t included such type argument info, we have to resolve them from Java models, which is expensive. We’re considering optimizing the index schema to include more information.
Feedback and Suggestions
Please don’t hesitate to try our product! Your feedback and suggestions are very important to us and will help shape our product in future. There are several ways to leave us feedback
- Leave your comment on this blog post
- Open an issue on our GitHub Issues page
Resources
Here is a list of links that are helpful to learn Java on Visual Studio Code.
- Learn more about Java on Visual Studio Code.
0 comments