Skip to content

perf(context): кэшировать разбор doc-комментариев символов#4175

Open
nixel2007 wants to merge 2 commits into
developfrom
claude/perf-description-cache
Open

perf(context): кэшировать разбор doc-комментариев символов#4175
nixel2007 wants to merge 2 commits into
developfrom
claude/perf-description-cache

Conversation

@nixel2007

@nixel2007 nixel2007 commented Jun 20, 2026

Copy link
Copy Markdown
Member

Зачем

Symbol tree перестраивается на каждый keystroke, и при сборке для каждого метода/переменной жадно вызывался MethodDescription.create(...) / VariableDescription.create(...) — это полный отдельный ANTLR-разбор грамматики doc-комментария (свои лексер + парсер + обход на каждый комментарий). Текст комментариев между нажатиями почти не меняется, поэтому этот разбор переделывался впустую.

По профилю набора текста (SSL 3.2, модуль УправлениеДоступом, 3733 строки, плотно документирован) видимая self-time кадров BSLDescriptionParser.* была ~9%, но их «подводная» часть — ANTLR-машинерия, которую они запускают, — учитывалась под общими ANTLR-кадрами. Суммарно разбор описаний составлял почти половину стоимости computeSymbolTree.

Что меняется

SymbolDescriptionCache — контент-адресный кэш (Caffeine, bounded 50k) разобранных описаний. Точки вызова в MethodSymbolComputer/VariableSymbolComputer идут через кэш вместо прямого …Description.create(...).

Ключ — сигнатуры токенов комментария line:charPositionInLine:text. Это намеренно захватывает:

  • текст — от него зависит результат разбора;
  • абсолютные позиции — от них зависят SimpleRange внутри описания, которые читают семантические токены (BslDocSemanticTokens/CommentSemanticTokens) и диагностики (MissingParameterDescription, LineLength).

Поэтому попадание в кэш всегда возвращает описание, корректное и по содержимому, и по диапазонам. Описания неизменяемы и являются чистой функцией входных токенов → переиспользование экземпляра (в т.ч. между документами и потоками параллельного populateContext) безопасно.

Правки внутри строки (большинство нажатий) не сдвигают номера строк → позиции и текст комментариев прежние → попадание. Вставка/удаление строки сдвигает позиции ниже правки → промах и корректный пере-разбор уже с новыми диапазонами.

Замеры

Эмуляция набора текста на УправлениеДоступом (JFR на keystroke-rebuild), best-case по hit-rate (правки в конце модуля / внутри строки, позиции комментариев стабильны):

rebuild p50 computeSymbolTree (self, JFR)
до кэша ~62 мс базовая
с кэшем ~39 мс ≈ −48% сэмплов

BSLDescriptionParser.* полностью уходит из топа hotspot'ов. Выигрыш масштабируется с плотностью doc-комментариев (SSL документирован плотно → крупный эффект; для слабо документированных модулей — меньше).

Проверка

  • compileJava зелёный.
  • Тесты, читающие описания и их диапазоны, зелёные: MethodSymbolComputerTest, MethodSymbolComputerConstructorTest, VariableSymbolComputer*, SymbolTreeComputerTest, BslDocSemanticTokensSupplierTest, CommentSemanticTokensSupplierTest, MissingParameterDescriptionDiagnosticTest, HoverProviderTest, HoverProviderTypeAwareTest, SignatureHelpProviderTest, SignatureHelpCommonModuleTest.

Изменение независимо от #4174 (другие файлы), базируется на develop.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HcK3qVwTH91rXtgprGmoWr


Generated by Claude Code

Summary by CodeRabbit

  • Refactor
    • Improved symbol description handling by caching method and variable details for documents currently open in the editor, reducing repeated parsing and improving responsiveness. For documents that aren’t open, behavior remains direct.
  • Tests
    • Added JUnit coverage to verify caching for opened vs non-opened documents and to confirm cache invalidation when documents are closed, cleared, or removed.

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8e9a51b0-0ae9-4fec-afc5-36d86fe54ec4

📥 Commits

Reviewing files that changed from the base of the PR and between e2829a5 and fa1e173.

📒 Files selected for processing (5)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/DocumentContext.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/MethodSymbolComputer.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/SymbolDescriptionIndex.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/VariableSymbolComputer.java
  • src/test/java/com/github/_1c_syntax/bsl/languageserver/context/computer/SymbolDescriptionIndexTest.java
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/MethodSymbolComputer.java
  • src/test/java/com/github/_1c_syntax/bsl/languageserver/context/computer/SymbolDescriptionIndexTest.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/VariableSymbolComputer.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/DocumentContext.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/SymbolDescriptionIndex.java

📝 Walkthrough

Walkthrough

Introduces SymbolDescriptionIndex, a workspace-scoped Spring component that caches MethodDescription and VariableDescription per opened document using Caffeine caches keyed by token signatures. Cache entries are evicted on document close, clear, or remove events. DocumentContext gains a wired field for the index, and MethodSymbolComputer and VariableSymbolComputer route all description creation through it.

Changes

Symbol Description Index

Layer / File(s) Summary
SymbolDescriptionIndex implementation
src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/SymbolDescriptionIndex.java
Defines SymbolDescriptionIndex as a workspace-scoped @Component with per-document Caffeine caches, methodDescription and variableDescription public entry points that cache for opened documents and bypass cache otherwise, Spring @EventListener handlers for eviction on close/clear/remove events, and private helpers for building deterministic token-signature cache keys and an inner DocumentDescriptions container.
Wire index into DocumentContext and computers
src/main/java/com/github/_1c_syntax/bsl/languageserver/context/DocumentContext.java, src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/MethodSymbolComputer.java, src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/VariableSymbolComputer.java
DocumentContext gains a symbolDescriptionIndex field with Lombok-generated getter and @Autowired setter. MethodSymbolComputer.createDescription and all four VariableSymbolComputer.createDescription overloads replace direct MethodDescription.create/VariableDescription.create calls with documentContext.getSymbolDescriptionIndex().methodDescription/variableDescription.
Tests
src/test/java/com/github/_1c_syntax/bsl/languageserver/context/computer/SymbolDescriptionIndexTest.java
Adds SymbolDescriptionIndexTest verifying: same instance returned on repeated calls for opened docs (caching), different instances for non-opened docs (bypass), non-null variable descriptions for both paths, cache clearing on document close (re-parsed instance differs), and tryClearDocument/removeDocument lifecycle paths followed by successful re-add and re-open.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • 1c-syntax/bsl-language-server#3753: Modifies the same MethodSymbolComputer.createDescription and VariableSymbolComputer creation points that this PR now routes through the new SymbolDescriptionIndex caching factory.

Poem

🐇 Hop, hop, little cache so bright,
Tokens hashed and keyed just right.
Open docs get memoized fast,
Closed ones cleared — no shadow cast.
Caffeine brews each symbol true,
This bunny's cache is good as new! ☕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: caching the parsing of doc-comments for symbols, which is the core optimization implemented across MethodSymbolComputer, VariableSymbolComputer, and the new SymbolDescriptionIndex.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/perf-description-cache

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/SymbolDescriptionCache.java (1)

70-72: Replace manual private constructor with @UtilityClass annotation.

This class is a pure static utility with no instance members. Replace the manual private constructor pattern with Lombok's @UtilityClass annotation to align with the repository's established coding standard and reduce boilerplate code.

Import lombok.experimental.UtilityClass and annotate the class:

Suggested change
import lombok.experimental.UtilityClass;

`@UtilityClass`
final class SymbolDescriptionCache {
  // remove private constructor
  // ...rest of class remains unchanged
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/SymbolDescriptionCache.java`
around lines 70 - 72, The SymbolDescriptionCache class uses a manual private
constructor pattern for a pure static utility class. Replace this boilerplate by
adding the import statement for lombok.experimental.UtilityClass at the top of
the file, annotating the SymbolDescriptionCache class with `@UtilityClass`, making
the class final, and removing the private SymbolDescriptionCache() constructor
method entirely. This aligns with the repository's coding standards and Lombok
provides the same behavior automatically.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/SymbolDescriptionCache.java`:
- Around line 70-72: The SymbolDescriptionCache class uses a manual private
constructor pattern for a pure static utility class. Replace this boilerplate by
adding the import statement for lombok.experimental.UtilityClass at the top of
the file, annotating the SymbolDescriptionCache class with `@UtilityClass`, making
the class final, and removing the private SymbolDescriptionCache() constructor
method entirely. This aligns with the repository's coding standards and Lombok
provides the same behavior automatically.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 83dfa483-2e6e-48fa-bf24-ed1066425676

📥 Commits

Reviewing files that changed from the base of the PR and between bcba086 and db6ccae.

📒 Files selected for processing (3)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/MethodSymbolComputer.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/SymbolDescriptionCache.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/VariableSymbolComputer.java

@nixel2007
nixel2007 force-pushed the claude/perf-description-cache branch from 5c1bb42 to e93b70e Compare June 20, 2026 07:25
@github-actions

Copy link
Copy Markdown
Contributor

Test Results

 3 516 files  ±0   3 516 suites  ±0   1h 32m 59s ⏱️ -12s
 3 455 tests ±0   3 437 ✅ ±0   18 💤 ±0  0 ❌ ±0 
20 730 runs  ±0  20 618 ✅ ±0  112 💤 ±0  0 ❌ ±0 

Results for commit e93b70e. ± Comparison against base commit 346bcef.

@nixel2007
nixel2007 force-pushed the claude/perf-description-cache branch from e93b70e to 251d168 Compare June 20, 2026 09:26

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/test/java/com/github/_1c_syntax/bsl/languageserver/context/computer/SymbolDescriptionIndexTest.java (1)

81-89: ⚡ Quick win

Strengthen variable-path assertions to validate cache semantics.

This block only checks non-null results, so a regression in opened-doc caching or not-opened bypass would still pass. Assert identity behavior here as you already do for methods.

Suggested test tightening
-    var openedResult = index.variableDescription(opened, opened.getComments(), Optional.empty());
-    assertThat(openedResult).as("описание переменной открытого документа получено").isNotNull();
-    assertThat(index.variableDescription(opened, opened.getComments(), Optional.empty()))
-      .as("повторный вызов открытого документа не падает").isNotNull();
+    var openedFirst = index.variableDescription(opened, opened.getComments(), Optional.empty());
+    var openedSecond = index.variableDescription(opened, opened.getComments(), Optional.empty());
+    assertThat(openedFirst).as("описание переменной открытого документа получено").isNotNull();
+    assertThat(openedSecond).as("у открытого документа описание переменной берётся из кэша").isSameAs(openedFirst);
 
     // Не-открытый документ — прямой разбор мимо кэша.
     var notOpened = TestUtils.getDocumentContext(varSource);
-    assertThat(index.variableDescription(notOpened, notOpened.getComments(), Optional.empty()))
-      .as("описание переменной не-открытого документа получено напрямую").isNotNull();
+    var notOpenedFirst = index.variableDescription(notOpened, notOpened.getComments(), Optional.empty());
+    var notOpenedSecond = index.variableDescription(notOpened, notOpened.getComments(), Optional.empty());
+    assertThat(notOpenedFirst).as("описание переменной не-открытого документа получено напрямую").isNotNull();
+    assertThat(notOpenedSecond).as("не-открытый документ не должен кешировать описание переменной")
+      .isNotSameAs(notOpenedFirst);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/com/github/_1c_syntax/bsl/languageserver/context/computer/SymbolDescriptionIndexTest.java`
around lines 81 - 89, The test currently only validates that the
variableDescription method returns non-null results but does not verify the
caching behavior for opened documents or the direct bypass for not-opened
documents. Strengthen the assertions by checking object identity: for the opened
document, verify that calling variableDescription twice on the same opened
instance returns the identical object (using identity assertion as already done
for methods in this test), demonstrating caching is working; the notOpened
document assertion can remain as-is since it's testing the direct bypass path.
This will catch regressions where caching logic or the not-opened bypass might
be broken while still returning non-null values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@src/test/java/com/github/_1c_syntax/bsl/languageserver/context/computer/SymbolDescriptionIndexTest.java`:
- Around line 81-89: The test currently only validates that the
variableDescription method returns non-null results but does not verify the
caching behavior for opened documents or the direct bypass for not-opened
documents. Strengthen the assertions by checking object identity: for the opened
document, verify that calling variableDescription twice on the same opened
instance returns the identical object (using identity assertion as already done
for methods in this test), demonstrating caching is working; the notOpened
document assertion can remain as-is since it's testing the direct bypass path.
This will catch regressions where caching logic or the not-opened bypass might
be broken while still returning non-null values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 96f42e5f-44e4-4d70-82b8-0ce6ce85f994

📥 Commits

Reviewing files that changed from the base of the PR and between 5c1bb42 and 251d168.

📒 Files selected for processing (5)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/DocumentContext.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/MethodSymbolComputer.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/SymbolDescriptionIndex.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/VariableSymbolComputer.java
  • src/test/java/com/github/_1c_syntax/bsl/languageserver/context/computer/SymbolDescriptionIndexTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/MethodSymbolComputer.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/context/computer/VariableSymbolComputer.java

@nixel2007
nixel2007 force-pushed the claude/perf-description-cache branch from 251d168 to e2829a5 Compare June 20, 2026 09:36
Symbol tree открытого документа перестраивается на каждый keystroke, и при
сборке для каждого метода/переменной жадно вызывался MethodDescription.create /
VariableDescription.create — полный отдельный ANTLR-разбор грамматики комментария
(по профилю набора текста — заметная доля стоимости перестроения), хотя текст
комментариев между нажатиями почти не меняется.

SymbolDescriptionIndex (@WorkspaceScope) хранит разобранные описания по URI и
отдаёт их повторно. Ключевые свойства:

- кэшируется только для ОТКРЫТЫХ в редакторе документов (isDocumentOpened);
  для не-открытых (populateContext, пакетный анализ из CLI / sonar-bsl-community-
  plugin) документ разбирается один раз — кэш бесполезен, поэтому путь идёт мимо
  него (прямой create), без затрат памяти;
- записи привязаны к URI и сбрасываются на закрытии/освобождении/удалении
  документа (Closed/Cleared/Removed); на изменение содержимого индекс НЕ
  сбрасывается (иначе терялся бы смысл), устаревшие записи вытесняются по размеру;
- ключ контент-адресный (line:charPositionInLine:text по токенам) — захватывает и
  текст (результат разбора), и позиции (SimpleRange описания, которые читают
  семантические токены и диагностики), поэтому попадание всегда корректно и по
  содержимому, и по диапазонам.

Замер: попадание в кэш ~48–51x быстрее разбора и ~14–26x меньше аллокаций; на
keystroke-rebuild документированного модуля computeSymbolTree по сэмплам ~−48%.
Память: на populate/batch — 0; на один открытый модуль (УправлениеДоступом,
3733 строки, 61 описание) — ~0.14 МБ ключей (сами описания общие с symbol tree),
освобождается на закрытии документа.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HcK3qVwTH91rXtgprGmoWr
@nixel2007
nixel2007 force-pushed the claude/perf-description-cache branch from e2829a5 to d334cb7 Compare June 20, 2026 09:38
@sonarqubecloud

Copy link
Copy Markdown

Правки, сдвигающие номера строк, на каждый ребилд плодят новое поколение
позиционных ключей; на крупных модулях (замер на УправлениеДоступомСлужебный,
48k строк: 7279 записей при потолке 8192 уже после нескольких блоков набора)
пер-документный кэш быстро упирается в maximumSize, удерживая устаревшие
описания. Добавлен expireAfterAccess(1 мин) к обоим кэшам: после паузы в наборе
устаревшие ключи отваливаются, не дожидаясь вытеснения по размеру.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HcK3qVwTH91rXtgprGmoWr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants