VS Code Java 10 月更新!效能顯著提升!

語言: CN / TW / HK

作者:Nick Zhu

大家好,歡迎來到 10 月版的 Visual Studio Code Java 更新!在這篇文章中,我們將深入解析最近程式碼補全的效能優化。

效能提升 – 更快的程式碼補全

隨著 Java Language Server 最近 1.0 版本 的釋出,我們對程式碼補全的效能進行了重大的改進。下圖比較了最近幾個版本之間的程式碼補全響應時間。對於補全型別、建構函式名等常見的場景,程式碼補全效能較之前版本( v0.80v0.81v.0.82 )有顯著的提升

效能改進總覽

程式碼補全引擎由三個階段組成:

  • 階段一 (P1) – 搜尋索引器以查詢建議
  • 階段二 (P2) – 轉換建議為補全資訊
  • 階段三 (P3) – 計算程式碼片段建議

根據我們的分析,我們發現所有三個階段都有改進的空間。下表顯示了我們在過去版本中所做的改進。我們將在下一節中更多地討論這些變化的細節。

0.80.0 0.81.0 0.82.0 1.0.0
減少Windows I/O操作 (P2) N/A
對常量/預設值進行優化 (P2) N/A
延遲解析通用程式碼片段 (P3) N/A N/A
優化匿名建構函式 (P2) N/A N/A N/A
JDT 搜尋引擎 – 優化 unit.complete() (P1) N/A N/A N/A
JDT 搜尋引擎 – 改進索引檔案的I/O操作 (P1) N/A N/A N/A
延遲TextEdit計算(P2) N/A N/A 計劃中

關鍵改動細節

版本 0.81.0 – 減少Windows I/O操作. #1831

在過去的效能測試中,我們發現很大一部分時間成本花在了計算檔案 URI上面。這個發現佐證了我們之前的觀察:由於JVM 中特定於平臺的檔案系統相關實現,Windows 平臺上的程式碼補全效能相對較差。通過刪除不必要的 URI 計算,我們提高了效能,尤其是在 Windows 平臺上。

版本 0.81.0 – 對常量/預設值進行優化. #1835

當我們完成一個常量欄位(例如 Constants.*)時,完成彈出視窗將在選擇列表中顯示建議的欄位名稱及其常量值(例如 Bit1 : int = 1)。我們的分析發現,當類包含大量常量欄位成員時,這會讓補全非常慢。這是因為我們從 AST Tree 計算欄位值,這在操作大檔案時開銷很大。

為了優化它,我們決定推遲解析常量值。程式碼補全會簡化建議標籤並僅顯示欄位名稱(例如 Bit1 :int)。當您將滑鼠懸停在 Javadoc 的完成項上時,才會在Javadoc 部分顯示它的常量值。

以下是一個擁有1400 多行和 150 多個常量欄位的類的欄位完成的效能比較。

版本 平均響應時間(毫秒)
0.80.0 1429ms
0.81.0 72ms

版本 0.82.0 – 延遲解析通用程式碼片段Delay resolving generic snippets. #1838

有兩種型別的程式碼段:

  • 通用片段(例如 foreach、fori、ifelse 等)
  • 型別定義片段(例如類、介面等)

對於通用程式碼段,它會在構建完成項的“TextEdit”之前評估具有給定上下文的模板,此類評估可能會很昂貴。現在我們將這類評估推遲到解決階段。當代碼補全完成項被建立完成之後,模板模式將作為佔位符填充。實際值在解決階段進行評估,這不會阻止完成專案的顯示。這也是一項關於“延遲解析 TextEdit”可以在多大程度上提高效能的實驗,並且在大多數情況下,它應該執行良好

版本 0.82.0 – 優化匿名建構函式Optimize for anonymous constructors. #1836

當我們想完成一個新的Runnable時, 期望的結果是這樣的:

Runnable() {}

它由兩部分組成

  • Runnable 名稱
  • 空白的body片段 body () {\n\t\n}

通過效能分析,我們發現 CodeFormatUtil.format 花費了大量時間。

為了有一個正確的縮排和行分隔符,它們被格式化為當前的首選項。格式化是昂貴的,並且為所有專案(有時多達數千)重複格式化相同的內容(空體)。為了改進它,我們將空體格式化進行了一次性的操作並在所有專案中重用它。

版本 1.0.0 – 提升程式碼補全搜尋速度

為了優化索引搜尋效能,我們做了兩個關鍵改動。

我們的效能分析顯示,索引查詢任務97% 的 CPU 時間用於從磁碟載入索引內容的 I/O操作。這是因為我們使用的索引機制傾向於節省記憶體並且在搜尋引擎中使用很少的快取。幾乎每個查詢都必須從磁碟重新載入索引內容。一種直接的優化是降低 I/O 的頻率。

Java 索引器由多個雜湊表組成,每個雜湊表用於記錄某種型別的程式碼部分,例如型別宣告、方法宣告、引用、方法引用等。典型的查詢作業從索引中讀取一個或多個雜湊表,然後連線這些將條目索引到目標結果中。

當我們完成型別/建構函式名稱(例如 Str或者new Str)時,索引查詢作業讀取兩個雜湊表,一個是用於查詢匹配型別名稱的 typeDecl 表,另一個是用於查詢類檔案路徑的 documentName 表宣告相應的型別。由於我們的目的只是完成型別名稱並自動匯入對應的package,因此 typeDecl 表足以滿足我們的要求,並且不需要類檔案路徑。我們的優化是隻讀取 typeDecl 索引表,結果證明少讀取一張索引表可以節省大量 I/O 成本。

  • 優化索引閱讀操作. #574464

這個改動來自於社群開發者對上游 JDT 專案的貢獻。 Java 索引使用 UTF-8 對索引字元進行編碼。載入索引時,我們會將它們解碼回來。由於大多數索引字元只是ASCII字元,我們優化了解碼方法,使其讀取ASCII更快。

未來計劃

我們上面列出的改進使自動完成速度更快,但我們還沒有完成。未來,效能仍然是我們的首要任務,我們將繼續優化自動完成效能。以下是我們在未來幾個月內計劃的一些專案

  • 延遲文字解析 (Lazy Resolve TextEdit)

由於大多數語言客戶端不支援完成項的延遲解析文字編輯,Java 語言伺服器必須計算完成響應中所有完成項的文字編輯。這是最昂貴的計算的原因。我們正在與作者合作探索對延遲解析文字編輯的支援。

  • 更高效的索引架構 More Efficient Indexer

當前索引資料對於建構函式等一些程式碼完成場景來說是不夠的。例如,建構函式完成需要知道該類是否具有泛型型別引數,並決定是否在建構函式引用中新增菱形<>。建構函式索引表沒有包含這樣的型別引數資訊,我們必須從 Java 模型中解析它們,此類的解析操作成本很大。我們正在考慮優化索引架構以包含更多資訊。

反饋與建議

請積極使用我們的產品!您的反饋和建議對我們非常重要,並將幫助我們做得更好。 有幾種方法可以給我們留下反饋

資源

以下連結和資源能幫助您更好地瞭解Java on Visual Studio Code的相關資訊