perf(references): ускорить reference-индекс — компараторы-константы и hash-карта#4174
Conversation
…анты 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
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThree files in the reference model package are updated: ChangesReference Model Comparator Extraction and Map Optimization
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|



Зачем
Профилирование набора текста (didChange на каждый keystroke →
DocumentContext.rebuild→ReferenceIndexFiller.fill) на реальной конфигурации (SSL 3.2, общий модульУправлениеДоступом) показало, что заметная часть стоимости перестроения документа — не ANTLR-парсинг, а накладные расходы reference-индекса. По JFR на перестроении модуля:Comparator.lambda$*(пересборка цепочек компараторов) — ~12% self-time;ConcurrentSkipListMap.cpr/doPutотSymbolOccurrenceRepository(навигация по внешней сортированной карте черезSymbol.compareTo) — ~9–11%.Итого ~20% self-time перестроения уходило на обслуживание индекса обращений, который полностью пересобирается на каждое нажатие клавиши.
Что меняется
Два независимых, поведение-сохраняющих изменения:
Компараторы
Symbol/SymbolOccurrenceвынесены вstatic final. РаньшеcompareToпересобирал цепочкуcomparing(...).thenComparing(...)(5 звеньев уSymbol, вложенную сURIиRanges.compareуSymbolOccurrence) на каждый вызов. ЭтиcompareToдёргаются при каждой навигации по сортированным структурам индекса.Внешняя карта
SymbolOccurrenceRepository:ConcurrentSkipListMap→ConcurrentHashMap. Карта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):
Память даже снижается: внешняя hash-карта на 177k ключей легче skip-list-карты.
JFR до/после на том же сценарии подтверждает: целевые кластеры упали с ~21% → ~5% self-time перестроения (компараторы 12%→2.4%, внешняя skip-list-навигация репозитория 9%→1.8%).
Проверка
compileJavaзелёный.ReferenceIndexTest,ReferenceIndexFillerTest,ReferenceIndexReferenceFinderTest,RenameProviderTest,SourceDefinedSymbolDeclarationReferenceFinderTest— зелёные.🤖 Generated with Claude Code
https://claude.ai/code/session_01HcK3qVwTH91rXtgprGmoWr
Generated by Claude Code
Summary by CodeRabbit