perf(context): мемоизировать findCommonModule (workspace-scoped кэш)#4186
Conversation
CF.findCommonModule(name) при заполнении индекса ссылок вызывается на каждый идентификатор (call-site, комплексный идентификатор, параметр процедуры) — десятки тысяч раз на каждый keystroke-ребилд — и каждый раз заново проходит по case-insensitive карте конфигурации (case-folding: CharacterData.getProperties, StringLatin1.compareToCI). Резолв общего модуля по имени зависит только от конфигурации воркспейса. Добавлен мемоизирующий ServerContext.findCommonModule поверх ограниченного Caffeine-кэша. Кэш оформлен как @WorkspaceScope @bean (CacheConfiguration, proxyMode=INTERFACES) и инжектится в ServerContext — один экземпляр на воркспейс (резолв зависит от конфигурации, поэтому общий singleton-кэш смешивал бы воркспейсы). Сбрасывается в ServerContext.clear(). Горячие вызывающие в ReferenceIndexFiller и MdoRefBuilder переведены на ServerContext.findCommonModule. Кэшируются и промахи (основной поток вызовов), поэтому кэш ограничен по размеру: на полной типовой конфигурации SSL 3.2 намерено ~32 000 уникальных имён, проходящих через findCommonModule; потолок взят с запасом (131072). Замер (JFR sampling, набор текста в УправлениеДоступомСлужебный SSL 3.2, 48 399 строк): MdoRefBuilder.getMdoRef self-time 3.68% -> 1.87%, CF.findCommonModule уходит из профиля ребилда.
📝 WalkthroughWalkthroughA workspace-scoped Caffeine cache ( ChangesCommon module cache and call site migration
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
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 |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java (1)
455-466: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winNormalize cache keys before memoization.
Line 464 currently caches by raw identifier text. Since resolution is case-insensitive, different casings of the same module name produce separate cache entries and reduce hit ratio. Normalize once (e.g.,
Locale.ROOT) beforecommonModuleCache.get(...).Proposed diff
+import java.util.Locale; import java.util.Optional; @@ public Optional<CommonModule> findCommonModule(String name) { - return commonModuleCache.get(name, key -> getConfiguration().findCommonModule(key)); + var normalizedName = name.toLowerCase(Locale.ROOT); + return commonModuleCache.get(normalizedName, key -> getConfiguration().findCommonModule(key)); }🤖 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/ServerContext.java` around lines 455 - 466, In the findCommonModule method, the name parameter used as a cache key should be normalized before being passed to commonModuleCache.get() to improve cache hit ratio. Since module name resolution is case-insensitive, normalize the name parameter using Locale.ROOT (for example, name.toLowerCase(Locale.ROOT)) before using it in the cache lookup to ensure that different casings of the same module name produce a single cache entry.
🤖 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/ServerContext.java`:
- Around line 455-466: In the findCommonModule method, the name parameter used
as a cache key should be normalized before being passed to
commonModuleCache.get() to improve cache hit ratio. Since module name resolution
is case-insensitive, normalize the name parameter using Locale.ROOT (for
example, name.toLowerCase(Locale.ROOT)) before using it in the cache lookup to
ensure that different casings of the same module name produce a single cache
entry.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 0386625b-3dc9-48a1-8e81-ad285b733564
📒 Files selected for processing (4)
src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.javasrc/main/java/com/github/_1c_syntax/bsl/languageserver/infrastructure/CacheConfiguration.javasrc/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFiller.javasrc/main/java/com/github/_1c_syntax/bsl/languageserver/utils/MdoRefBuilder.java
Резолв общего модуля регистронезависимый, поэтому ключ кэша нормализуется через toLowerCase(Locale.ROOT) перед обращением — разные написания одного имени дают одну запись, лучше hit-ratio и меньше distinct-ключей (как TypeRegistry.resolve).
…мализации) toLowerCase(Locale.ROOT) на каждый вызов реинжектил на горячий путь посимвольное сворачивание регистра + аллокацию строки — ровно тот расход, ради устранения которого кэш и делался. Выгода (схлопывание редких регистровых вариантов одного имени) околонулевая: distinct-ключей на ssl_3_2 ~32k что с нормализацией, что без. Возвращаем сырой ключ; резолв внутри остаётся регистронезависимым.
…text Холодные пути (диагностики MissingEventSubscriptionHandler / CommonModuleAssign / ScheduledJobHandler и documentlink SeeReferenceDocumentLinkSupplier) переведены с прямого getConfiguration().findCommonModule на ServerContext.findCommonModule — теперь все вызовы идут через один кэш.
|



Описание
CF.findCommonModule(name)при заполнении индекса ссылок вызывается на каждый идентификатор (call-site, комплексный идентификатор, параметр процедуры) — десятки тысяч раз на каждый keystroke-ребилд — и каждый раз заново проходит по case-insensitive карте конфигурации (CaseInsensitiveMap): посимвольное сворачивание регистра запроса (CharacterData.getProperties,StringLatin1.compareToCI).Резолв общего модуля по имени зависит только от конфигурации воркспейса. Добавлен мемоизирующий
ServerContext.findCommonModuleповерх ограниченного Caffeine-кэша. Все вызывающие переведены на него:ReferenceIndexFiller(4 места) иMdoRefBuilder(горячий путь), а также диагностикиMissingEventSubscriptionHandler/CommonModuleAssign/ScheduledJobHandlerи documentlinkSeeReferenceDocumentLinkSupplier(холодные пути) — теперь весь резолв идёт через один кэш.Почему так
@WorkspaceScope @Bean(CacheConfiguration,proxyMode = INTERFACES), инжектится вServerContext. Один экземпляр на воркспейс: резолв зависит от конфигурации, поэтому общий singleton-кэш смешивал бы воркспейсы.ServerContextсоздаётся внутриWorkspaceContextHolder.forUri(...)и уже потребляет такие же@WorkspaceScope-бины (populateContextExecutorи др.) — проверенный паттерн.@Cacheable— AOP-прокси + SpEL на ультра-горячем пути (десятки тысяч вызовов на keystroke) и непропуск внутренних self-вызовов.maximumSize): кэшируются и промахи (основной поток вызовов). На полной типовой конфигурации SSL 3.2 намерено ~32 000 уникальных имён, проходящих черезfindCommonModule; потолок взят с запасом — 131072.-prof gc): попадание по сырому ключу — 5.5 ns/op, ~0 B/op; вариант сtoLowerCase(Locale.ROOT)— 141 ns/op, 84.5 B/op (×26 по CPU + аллокация на каждый вызов). Сам резолв внутри остаётся регистронезависимым.ServerContext.clear().Замер (CPU)
JFR sampling, рабочий сценарий набора текста в общем модуле
УправлениеДоступомСлужебный(SSL 3.2, 48 399 строк):MdoRefBuilder.getMdoRefself-timeCF.findCommonModule(inclusive)CharacterData.getProperties(case-folding)StringLatin1.compareToCIЧеклист
Общие
ReferenceIndexFillerTest/ReferenceIndexTest/ReferenceIndexReferenceFinderTest/ServerContextTestи тестами затронутых диагностик)🤖 Generated with Claude Code