perf(types): точечная инвалидация memo членов вместо глобальной эпохи#4280
perf(types): точечная инвалидация memo членов вместо глобальной эпохи#4280nixel2007 wants to merge 7 commits into
Conversation
На пакетном анализе AnalyzeCommand.getFileInfoFromFile перестраивает каждый документ (rebuildDocument), поэтому на каждый из 13090 файлов летел DocumentContextContentChangedEvent, а TypeRegistry.invalidateMembersCache глобально сдвигал membersEpoch — обесценивая memo членов ВСЕХ типов и эпоха-кэшированный name-индекс глобальной области. При параллельном анализе один поток сносил кэш посреди резолва другого, из-за чего getMembers и globalNameIndex пересобирались тысячи раз вместо одного. Замер на cpm (подмножество Catalogs, 2071 файл): computeMembers 25981→496 вызовов на 440 различных типов, globalNameIndex 10440→14. Фаза анализа полной конфигурации: computeMembers 7.5%→0%, globalNameIndex 7.8%→0% CPU, wall 7:26→6:27 (−13%). Инвалидация на изменение содержимого BSL-документа теперь точечная — только по затронутому типу (TypeRegistry.invalidateMembers), без сдвига глобальной эпохи; общий модуль дополнительно освежает член GLOBAL_CONTEXT и name-индекс (GlobalScopeProvider.invalidateNameIndex). Это согласовано с per-document моделью инвалидации прочих индексов системы типов (InferredExpressionTypeIndex и др.), где правка документа сбрасывает кэш только этого документа. OScript оставлен на широкой инвалидации через эпоху: типы .os связаны межфайловым наследованием (&Расширяет), правка родителя меняет унаследованные члены наследника в другом документе. registerGlobalPropertyType сдвигает эпоху только при реальном росте набора типов, а не на каждую повторную пометку. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLvkKQCwBjTHwuZ4EGBQK6
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughModule and global-scope member caches now use targeted generation-based invalidation. Registry epoch updates are narrowed, module rebuilds refresh affected caches, OScript inheritance changes propagate invalidation to subtypes, and benchmarks and tests cover the updated behavior. ChangesMember cache invalidation
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant DocumentContextContentChangedEvent
participant OScriptModuleMembersProvider
participant TypeRelations
participant TypeRegistry
participant GlobalScopeProvider
DocumentContextContentChangedEvent->>OScriptModuleMembersProvider: notify changed document
OScriptModuleMembersProvider->>TypeRelations: resolve transitive subtypes
OScriptModuleMembersProvider->>TypeRegistry: invalidate affected TypeRefs
GlobalScopeProvider->>TypeRegistry: read current member sources
GlobalScopeProvider->>GlobalScopeProvider: rebuild global name index when sources change
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/test/java/com/github/_1c_syntax/bsl/languageserver/types/registry/ConfigurationModuleMembersProviderTest.java (1)
63-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert that the edited module’s cached list was actually replaced.
The final content assertion also passes if module invalidation never occurs. Capture the module members before publishing the event and assert the subsequent collection is not the same instance.
Proposed test strengthening
- assertThat(typeRegistry.getMembers(moduleType, FileType.BSL)) + var moduleMembersBefore = typeRegistry.getMembers(moduleType, FileType.BSL); + assertThat(moduleMembersBefore) .extracting(member -> member.name()).contains("НеУстаревшаяПроцедура"); ... - assertThat(typeRegistry.getMembers(moduleType, FileType.BSL)) + var moduleMembersAfter = typeRegistry.getMembers(moduleType, FileType.BSL); + assertThat(moduleMembersAfter).isNotSameAs(moduleMembersBefore); + assertThat(moduleMembersAfter) .extracting(member -> member.name()).contains("НеУстаревшаяПроцедура");As per coding guidelines, maintain or improve test coverage using appropriate test frameworks.
🤖 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/types/registry/ConfigurationModuleMembersProviderTest.java` around lines 63 - 76, Strengthen the test around ConfigurationModuleMembersProviderTest by capturing the edited module’s member collection before publishing DocumentContextContentChangedEvent, then assert the collection returned afterward is not the same instance while preserving the existing content assertion. Keep the arrayType same-instance assertion unchanged.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.
Inline comments:
In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/TypeRegistry.java`:
- Around line 608-611: Make targeted invalidation generation-based: in
TypeRegistry.invalidateMembers, increment a canonical per-type generation keyed
by the resolved source so alternate TypeRefs share it, and have getMembers
capture and validate that generation before publishing cached results. In
GlobalScopeProvider at the specified site, include the GLOBAL_CONTEXT generation
in GlobalIndex and reject publication from computations created under an older
generation. Apply the TypeRegistry change in TypeRegistry.java:608-611 and the
GlobalIndex change in GlobalScopeProvider.java:175-176.
---
Nitpick comments:
In
`@src/test/java/com/github/_1c_syntax/bsl/languageserver/types/registry/ConfigurationModuleMembersProviderTest.java`:
- Around line 63-76: Strengthen the test around
ConfigurationModuleMembersProviderTest by capturing the edited module’s member
collection before publishing DocumentContextContentChangedEvent, then assert the
collection returned afterward is not the same instance while preserving the
existing content assertion. Keep the arrayType same-instance assertion
unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4218a74b-ed83-417f-a98c-9785edf3bfd5
📒 Files selected for processing (5)
src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/ConfigurationModuleMembersProvider.javasrc/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/GlobalScopeProvider.javasrc/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/TypeRegistry.javasrc/test/java/com/github/_1c_syntax/bsl/languageserver/types/registry/ConfigurationModuleMembersProviderTest.javasrc/test/java/com/github/_1c_syntax/bsl/languageserver/types/registry/TypeRegistryScopedSourcesTest.java
| public void invalidateMembers(TypeRef ref) { | ||
| for (var fileType : FileType.values()) { | ||
| membersCache.remove(new MembersKey(ref, fileType)); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Make targeted invalidation generation-based.
Both APIs clear cached values without changing a version, allowing an in-flight stale computation to republish data after invalidation.
src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/TypeRegistry.java#L608-L611: increment a canonical per-type generation and validate it ingetMembers; this should also cover alternateTypeRefs resolving to the same source.src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/GlobalScopeProvider.java#L175-L176: include theGLOBAL_CONTEXTgeneration inGlobalIndexand reject publication from an older generation.
📍 Affects 2 files
src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/TypeRegistry.java#L608-L611(this comment)src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/GlobalScopeProvider.java#L175-L176
🤖 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/types/registry/TypeRegistry.java`
around lines 608 - 611, Make targeted invalidation generation-based: in
TypeRegistry.invalidateMembers, increment a canonical per-type generation keyed
by the resolved source so alternate TypeRefs share it, and have getMembers
capture and validate that generation before publishing cached results. In
GlobalScopeProvider at the specified site, include the GLOBAL_CONTEXT generation
in GlobalIndex and reject publication from computations created under an older
generation. Apply the TypeRegistry change in TypeRegistry.java:608-611 and the
GlobalIndex change in GlobalScopeProvider.java:175-176.
#4280) По замечанию CodeRabbit: точечная инвалидация через remove не защищала от гонки — параллельный незавершённый computeMembers мог дописать устаревший результат в кэш уже ПОСЛЕ инвалидации (при точечной инвалидации эпоха не двигается, поэтому устаревшая запись принималась следующим чтением). Инвалидация теперь через пер-типовое поколение: invalidateMembers инкрементирует поколение ключа, getMembers штампует запись поколением, снятым ДО вычисления, и отвергает публикацию из устаревшего поколения. Аналогично GlobalScopeProvider: name-индекс несёт поколение, invalidateNameIndex его инкрементирует, globalMember отвергает устаревшую пересборку. Добавлен детерминированный тест гонки (источник инициирует инвалидацию во время собственного вычисления). Плюс метод-референсы вместо лямбд в тесте (Sonar maintainability на новом коде). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLvkKQCwBjTHwuZ4EGBQK6
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/GlobalScopeProvider.java (1)
208-213: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winUse
compareAndSetto prevent cache thrashing.Unconditionally setting
globalIndexRefcan lead to a race condition where a slow thread overwrites a fresher index (computed by another thread) with its own stale index. While this stale index will be safely rejected on the next read (because its generation won't matchnameIndexGeneration), it will force an unnecessary recomputation.Using
compareAndSetensures that we don't overwrite a newer cache entry with an older one.⚡ Proposed optimization
if (index == null || index.epoch() != epoch || index.generation() != generation) { - index = new GlobalIndex(epoch, generation, Map.of( + var newIndex = new GlobalIndex(epoch, generation, Map.of( FileType.BSL, globalNameIndex(FileType.BSL), FileType.OS, globalNameIndex(FileType.OS))); - globalIndexRef.set(index); + globalIndexRef.compareAndSet(index, newIndex); + index = newIndex; }🤖 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/types/registry/GlobalScopeProvider.java` around lines 208 - 213, Update the cache refresh logic in GlobalScopeProvider to publish the newly created GlobalIndex via globalIndexRef.compareAndSet, using the previously observed index as the expected value, instead of unconditionally calling set. Preserve the existing epoch and generation validation while ensuring a slower thread cannot overwrite a newer cached index.
🤖 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/types/registry/GlobalScopeProvider.java`:
- Around line 208-213: Update the cache refresh logic in GlobalScopeProvider to
publish the newly created GlobalIndex via globalIndexRef.compareAndSet, using
the previously observed index as the expected value, instead of unconditionally
calling set. Preserve the existing epoch and generation validation while
ensuring a slower thread cannot overwrite a newer cached index.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 0080e0c7-c7db-4722-b0d9-cb924458ee45
📒 Files selected for processing (4)
src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/GlobalScopeProvider.javasrc/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/TypeRegistry.javasrc/test/java/com/github/_1c_syntax/bsl/languageserver/types/registry/ConfigurationModuleMembersProviderTest.javasrc/test/java/com/github/_1c_syntax/bsl/languageserver/types/registry/TypeRegistryScopedSourcesTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
- src/test/java/com/github/_1c_syntax/bsl/languageserver/types/registry/ConfigurationModuleMembersProviderTest.java
- src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/TypeRegistry.java
…ору-источнику Отдельный счётчик поколения name-индекса дублировал версионирование, которое уже есть уровнем ниже: индекс — производная от getMembers(GLOBAL_CONTEXT), а тот версионируется эпохой и пер-типовым поколением. Провайдер из-за этого делал два вызова об одном факте (invalidateMembers(GLOBAL_CONTEXT) + invalidateNameIndex). Теперь GlobalIndex хранит наборы-источники, из которых собран, и сверяет их идентичность: getMembers отдаёт тот же экземпляр списка, пока memo живо, и новый — после любой инвалидации. Этого достаточно и для актуальности, и для отбрасывания индекса, собранного параллельно из устаревших членов, поэтому nameIndexGeneration и invalidateNameIndex удалены вместе с лишним вызовом у провайдера. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLvkKQCwBjTHwuZ4EGBQK6
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/GlobalScopeProvider.java (2)
258-260: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valuePre-size the
HashMapto avoid rehashing.Since the size of the
memberscollection is known and each member can potentially contribute two entries (Russian and English), initializing theHashMapwith a sufficient initial capacity avoids runtime rehashing overhead.⚡ Proposed fix
- private Map<String, MemberDescriptor> globalNameIndex(Collection<MemberDescriptor> members) { - var map = new HashMap<String, MemberDescriptor>(); - for (var member : members) { + private Map<String, MemberDescriptor> globalNameIndex(Collection<MemberDescriptor> members) { + // Both RU and EN names might be added, so size the map to avoid rehashing + var map = new HashMap<String, MemberDescriptor>(members.size() * 2); + for (var member : members) {🤖 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/types/registry/GlobalScopeProvider.java` around lines 258 - 260, Update globalNameIndex to initialize its HashMap with an initial capacity based on members.size() multiplied by two, accounting for possible Russian and English entries and avoiding rehashing.
181-194: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winUse
compareAndSetto prevent stale cache overwrites and update outdated Javadoc.If two threads concurrently update the index after an invalidation, a thread with an older
bslSource/osSourcecan overwrite a newer cached index usingset, causing an unnecessary cache miss on the next call. UsingcompareAndSetprevents this race condition.Additionally, please update the method's Javadoc (around line 169). It still references
пересобираемому при смене эпохи членов ({@linkTypeRegistry#membersEpoch()}), which contradicts the new identity-based caching logic. As per coding guidelines, keep documentation up to date with code changes.⚡ Proposed fix
- if (index == null || index.bslSource() != bslSource || index.osSource() != osSource) { - index = new GlobalIndex(bslSource, osSource, Map.of( - FileType.BSL, globalNameIndex(bslSource), - FileType.OS, globalNameIndex(osSource))); - globalIndexRef.set(index); - } + if (index == null || index.bslSource() != bslSource || index.osSource() != osSource) { + var newIndex = new GlobalIndex(bslSource, osSource, Map.of( + FileType.BSL, globalNameIndex(bslSource), + FileType.OS, globalNameIndex(osSource))); + globalIndexRef.compareAndSet(index, newIndex); + index = newIndex; + }🤖 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/types/registry/GlobalScopeProvider.java` around lines 181 - 194, Update the global index cache logic in the relevant provider method to publish newly built GlobalIndex instances with globalIndexRef.compareAndSet(index, newIndex) instead of unconditional set, preserving the existing cached value when another thread has already installed a newer index. Also revise that method’s Javadoc to remove the membersEpoch-based rebuild description and document the current source-identity caching behavior.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/types/registry/GlobalScopeProvider.java`:
- Around line 258-260: Update globalNameIndex to initialize its HashMap with an
initial capacity based on members.size() multiplied by two, accounting for
possible Russian and English entries and avoiding rehashing.
- Around line 181-194: Update the global index cache logic in the relevant
provider method to publish newly built GlobalIndex instances with
globalIndexRef.compareAndSet(index, newIndex) instead of unconditional set,
preserving the existing cached value when another thread has already installed a
newer index. Also revise that method’s Javadoc to remove the membersEpoch-based
rebuild description and document the current source-identity caching behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: efae6277-8537-4700-8611-7278195969ac
📒 Files selected for processing (2)
src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/ConfigurationModuleMembersProvider.javasrc/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/GlobalScopeProvider.java
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/ConfigurationModuleMembersProvider.java
Дерево зависимостей проекта даёт в jmh-архиве больше 65535 записей, поэтому задача jmhJar падала с «Archive contains more than 65535 entries» — запустить ./gradlew jmh было нельзя вообще. Включаем zip64 у архива. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLvkKQCwBjTHwuZ4EGBQK6
…ления Полный прогон анализа на cpm не разрешает накладной расход проверки поколения: разброс baseline между прогонами доходит до 1м41с, а проверка добавляет один поиск в хеш-карте на вызов. Микробенчмарк меряет это напрямую. realGetMembersHit — реальный TypeRegistry (440 типов, memo прогрето) на попадании. lookupEpochOnly и lookupEpochAndGeneration — изолированные модели проверки актуальности «до» и «после» на картах того же размера; их разница и есть цена добавленного поиска. Параметр invalidatedTypes задаёт размер карты поколений: 0 — правок не было, 440 — инвалидировались все типы рабочей области. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLvkKQCwBjTHwuZ4EGBQK6
…алидации - globalMember публикует пересобранный индекс через compareAndSet: параллельный поток мог опубликовать индекс по более свежим наборам, и затирать его своим не нужно — иначе следующее чтение зря пересоберёт индекс; - javadoc globalMember больше не ссылается на эпоху членов как на критерий пересборки: после перехода на сверку наборов-источников это стало неверно; - globalNameIndex сразу задаёт ёмкость карты (до двух записей на член, ru и en); - тест инвалидации проверяет, что memo отредактированного модуля именно пересобрано (новый экземпляр списка): проверка только по содержимому проходила бы и в случае, когда инвалидации не произошло вовсе. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLvkKQCwBjTHwuZ4EGBQK6
|
@coderabbitai учтено, спасибо — разобрал и actionable, и все nit'ы. Major (generation-based invalidation). Точечная инвалидация переведена на пер-типовое поколение (901aa63): Про «alternate TypeRefs resolving to the same source»: Про поколение name-индекса — оно в итоге убрано совсем (880d75d). Отдельный счётчик дублировал версионирование, которое уже есть уровнем ниже: индекс — производная от Nit'ы (6d1e8f5):
По цене guard'а (на случай вопроса, стоил ли он того): микробенчмарк JMH |
|
Tip For best results, initiate chat on the files or code changes.
🐇✨ ✏️ Learnings added
If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
…й эпохи BSL перевели на точечную инвалидацию раньше, а .os остался на широком сдвиге эпохи — то есть в OneScript-рабочей области фикса не было вообще: каждый rebuildDocument на пакетном анализе по-прежнему обесценивал memo всех типов. Вдобавок эпоха одна на реестр, поэтому правка .os сбрасывала и кэши BSL-типов, хотя источники членов строго разделены по языку (resolveMemberSources читает memberSources.get(fileType)) и пересекаться не могут. Теперь OScriptModuleMembersProvider на изменение содержимого сбрасывает memo правленого документа и его наследников, обходя TypeRelations.subtypes вширь. Обход обязан быть транзитивным: inheritedMembers копирует к себе результат getMembers родителя, поэтому в memo наследника лежит снимок членов родителя — сброса одного родителя мало, снимок протухает на всю глубину иерархии. Реализаторов интерфейсов обходить не нужно: &Реализует членов не приносит, inheritedMembers резолвит только родительский класс. Повторные посещения отсекаются по URI, это же защищает от циклов в объявлениях наследования. Листенер TypeRegistry.invalidateMembersCache удалён целиком — после перевода обоих языков на точечную инвалидацию ему нечего делать. Вместе с ним ушёл последний @eventlistener реестра (оба импорта), а публичный membersEpoch() остался без потребителей после перехода name-индекса на сверку наборов- источников и удалён как мёртвый. membersEpoch теперь версионирует ровно структурные мутации реестра. Тест editingBaseClassRebuildsMembersOfGrandchildTransitively на фикстуре extends-lib (База ← Промежуточный ← Дочерний) правит базу и требует пересборки memo внука; проверен на красноту — без обхода наследников падает. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLvkKQCwBjTHwuZ4EGBQK6
|



На пакетном анализе
AnalyzeCommand.getFileInfoFromFileперестраивает каждый документ (rebuildDocument), поэтому на каждый файл летелDocumentContextContentChangedEvent, аTypeRegistry.invalidateMembersCacheглобально сдвигалmembersEpoch— обесценивая memo членов всех типов и name-индекс глобальной области. При параллельном анализе один поток сносил кэш посреди резолва другого, из-за чегоgetMembersиglobalNameIndexпересобирались тысячи раз вместо одного.Что сделано
Инвалидация memo членов на правку документа теперь точечная — и для BSL, и для OScript, согласованно с per-document моделью прочих индексов системы типов (
InferredExpressionTypeIndexи др.), где правка документа сбрасывает кэш только этого документа.ConfigurationModuleMembersProvider): сбрасывается memo только затронутого типа (TypeRegistry.invalidateMembers); общий модуль дополнительно освежает членGLOBAL_CONTEXT.OScriptModuleMembersProvider): сбрасывается memo правленого документа и его наследников транзитивно — обходTypeRelations.subtypesвширь. Обход обязан быть транзитивным:inheritedMembersкопирует к себе результатgetMembersродителя, поэтому в memo наследника лежит снимок членов родителя, и сброса одного родителя мало — снимок протухает на всю глубину иерархии. Реализаторов интерфейсов обходить не нужно:&Реализуетчленов не приносит. Отсечка по URI защищает и от циклов в объявлениях наследования.TypeRegistry.invalidateMembersCacheудалён целиком — после перевода обоих языков на точечную инвалидацию ему нечего делать.membersEpochтеперь версионирует ровно структурные мутации реестра.getMembersотдаёт тот же экземпляр списка, пока memo живо, и новый — после любой инвалидации).computeMembersдописывает в кэш уже после инвалидации) закрыта пер-типовым поколением:getMembersштампует запись поколением, снятым до вычисления, и отвергает устаревшую публикацию.Замеры
Надёжная метрика — детерминированный счётчик вызовов
computeMembers(пересборок memo), не CPU-сэмплирование:computeMembers25981 → 496,globalNameIndex10440 → 14.computeMembers16044 → 769 (~21×).MembersCacheLookup): пер-типовое поколение добавляет к попаданию в memo ~1.9 нс на пустой карте поколений и ~3.4 нс на заполненной; реальныйgetMembersна попадании — 5.7/8.4 нс. На масштабе прогона это десятки миллисекунд.Wall-time на профилировочной машине слишком шумит, чтобы заявлять проценты (тот же jar давал разброс до 1.8×), поэтому эффект показан по числу пересборок.
Claude-Session: https://claude.ai/code/session_01HLvkKQCwBjTHwuZ4EGBQK6
Описание
Связанные задачи
Closes
Чеклист
Общие
gradlew precommit)Для диагностик
Дополнительно
Summary by CodeRabbit