Skip to content

perf(references): ускорить reference-индекс — компараторы-константы и hash-карта#4174

Merged
nixel2007 merged 2 commits into
developfrom
claude/perf-reference-index-structures
Jun 20, 2026
Merged

perf(references): ускорить reference-индекс — компараторы-константы и hash-карта#4174
nixel2007 merged 2 commits into
developfrom
claude/perf-reference-index-structures

Conversation

@nixel2007

@nixel2007 nixel2007 commented Jun 20, 2026

Copy link
Copy Markdown
Member

Зачем

Профилирование набора текста (didChange на каждый keystroke → DocumentContext.rebuildReferenceIndexFiller.fill) на реальной конфигурации (SSL 3.2, общий модуль УправлениеДоступом) показало, что заметная часть стоимости перестроения документа — не ANTLR-парсинг, а накладные расходы reference-индекса. По JFR на перестроении модуля:

  • Comparator.lambda$* (пересборка цепочек компараторов) — ~12% self-time;
  • ConcurrentSkipListMap.cpr/doPut от SymbolOccurrenceRepository (навигация по внешней сортированной карте через Symbol.compareTo) — ~9–11%.

Итого ~20% self-time перестроения уходило на обслуживание индекса обращений, который полностью пересобирается на каждое нажатие клавиши.

Что меняется

Два независимых, поведение-сохраняющих изменения:

  1. Компараторы Symbol/SymbolOccurrence вынесены в static final. Раньше compareTo пересобирал цепочку comparing(...).thenComparing(...) (5 звеньев у Symbol, вложенную с URI и Ranges.compare у SymbolOccurrence) на каждый вызов. Эти compareTo дёргаются при каждой навигации по сортированным структурам индекса.

  2. Внешняя карта SymbolOccurrenceRepository: ConcurrentSkipListMapConcurrentHashMap. Карта Symbol → обращения использовалась только для lookup (getAllBySymbol/save/deleteAll), но как skip-list навигировала по ключам через Symbol.compareTo за O(log n) на каждой операции. Symbol — record (готовые hashCode/equals), поэтому hash-карта ищет ключ за O(1) без сравнений. Внутреннее множество обращений остаётся ConcurrentSkipListSet, поэтому порядок в getAllBySymbol сохраняется и read-side (find-references / rename) не меняется.

Изменения дополняют друг друга: hash-карта убирает сравнения снаружи, а вынесённый компаратор удешевляет сравнения, оставшиеся во внутреннем сортированном множестве.

Замеры

Микробенчмарк структуры reference-индекса (горячая операция «очистить + наполнить индекс модуля», 2000 обращений поверх 20k фоновых; на одну keystroke):

время refill find-references retained-память (реальный индекс)
было (skiplist + per-call cmp) базовая
этот PR (hash-outer + skiplist-inner + cmp-константы) ~12× ~3.6× −1.4%

Память даже снижается: внешняя hash-карта на 177k ключей легче skip-list-карты.

JFR до/после на том же сценарии подтверждает: целевые кластеры упали с ~21% → ~5% self-time перестроения (компараторы 12%→2.4%, внешняя skip-list-навигация репозитория 9%→1.8%).

Проверка

  • compileJava зелёный.
  • Тесты ReferenceIndexTest, ReferenceIndexFillerTest, ReferenceIndexReferenceFinderTest, RenameProviderTest, SourceDefinedSymbolDeclarationReferenceFinderTest — зелёные.
  • Семантика сохранена: внешний порядок карты нигде не наблюдается (карта только для lookup), а порядок обращений во внутреннем множестве не меняется.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HcK3qVwTH91rXtgprGmoWr


Generated by Claude Code

Summary by CodeRabbit

  • Refactor
    • Optimized symbol comparison and reference indexing performance through internal restructuring. These improvements enhance responsiveness when navigating and managing code references in larger projects.

claude added 2 commits June 20, 2026 06:43
…анты

compareTo обоих классов пересобирал цепочку comparing/thenComparing на
каждый вызов. Эти compareTo дёргаются при каждой навигации по сортированным
структурам reference-индекса, который перестраивается на каждый keystroke.
По JMH на горячем пушке refill это давало 18.7 МБ мусора на операцию;
вынос компаратора в static final убирает ~74× аллокаций без изменения
поведения сравнения.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01HcK3qVwTH91rXtgprGmoWr
…ap → ConcurrentHashMap

Внешняя карта Symbol → обращения использовалась только для lookup
(getAllBySymbol / save / deleteAll), но как ConcurrentSkipListMap навигировала
по ключам через Symbol.compareTo на каждой операции — на keystroke это были
тысячи O(log n) сравнений (ConcurrentSkipListMap.cpr ~10% rebuild по JFR).
ConcurrentHashMap ищет ключ за O(1) по hashCode/equals (Symbol — record).

Внутреннее множество остаётся ConcurrentSkipListSet, поэтому порядок
обращений getAllBySymbol сохраняется и read-side не меняется. По JMH на
горячем refill это ~12× к baseline, на чтении find-references ~3.6×, а
retained-память реального индекса даже -1.4% (внешняя hash-карта на 177k
ключей легче skip-list-карты, см. ReferenceIndexMemoryTest).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01HcK3qVwTH91rXtgprGmoWr
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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: 8902e337-5585-4218-bf30-1a98410ff043

📥 Commits

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

📒 Files selected for processing (3)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/references/model/Symbol.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/references/model/SymbolOccurrence.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/references/model/SymbolOccurrenceRepository.java

📝 Walkthrough

Walkthrough

Three files in the reference model package are updated: Symbol and SymbolOccurrence each extract their inline comparing(...).thenComparing(...) chain into a private static final COMPARATOR constant, and compareTo delegates to it. SymbolOccurrenceRepository switches its outer index map from ConcurrentSkipListMap to ConcurrentHashMap, keeping the inner ConcurrentSkipListSet for per-symbol ordering.

Changes

Reference Model Comparator Extraction and Map Optimization

Layer / File(s) Summary
Static COMPARATOR constants in Symbol and SymbolOccurrence
...references/model/Symbol.java, ...references/model/SymbolOccurrence.java
Both records introduce a private static final Comparator<T> COMPARATOR holding the full ordering chain. compareTo in each is updated to return COMPARATOR.compare(this, other) after the existing null check. Explicit java.util.Comparator imports are added.
Repository outer map switched to ConcurrentHashMap
...references/model/SymbolOccurrenceRepository.java
The occurrencesToSymbols field is initialized as a ConcurrentHashMap instead of ConcurrentSkipListMap. The import is updated accordingly and the field Javadoc is rewritten to describe hash-based O(1) symbol lookup while noting the inner ConcurrentSkipListSet still preserves ordered occurrences.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐇 Hop, hop, I cached the compare,
No more rebuilding — so light, so fair!
The outer map hashes with speedy delight,
While inner sets keep their sorted-list might.
One constant to rule them, one static to bind —
A tidier burrow for the quick-footed mind! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 'perf(references): ускорить reference-индекс — компараторы-константы и hash-карта' clearly and specifically summarizes the main performance optimizations: extracting comparators as constants and replacing the skip-list map with a hash map for the reference index.
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-reference-index-structures

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.

@nixel2007
nixel2007 enabled auto-merge June 20, 2026 06:48
@sonarqubecloud

Copy link
Copy Markdown

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