Суть
В MetadataCollectionSpecializer материализация членов коллекций устроена как двойной цикл «по generic-членам базовой коллекции × по детям», и данные ребёнка (имя, returnType) пересчитываются внутри цикла по generic-членам, хотя от конкретного члена они не зависят.
materializeForMdos (группа коллекций, Метаданные.Документы.*)
static List<MemberDescriptor> buildGroupCollectionMembers(...) {
var raw = typeRegistry.getMembers(baseRef, FileType.BSL);
for (var member : raw) {
if (member.generic()) {
materializeForMdos(typeRegistry, member, mdos, elementTypeRef, result); // ← на каждый generic-член
} ...
}
}
private static void materializeForMdos(..., MemberDescriptor member, List<MD> mdos, ...) {
for (var mdo : mdos) {
var mdoName = mdo.getName();
var perMdoRef = typeRegistry.intern(TypeKind.PLATFORM, elementTypeRef.qualifiedName() + "." + mdoName);
sink.add(materializeChildMember(member,
BilingualString.of(mdoName), // ← пересоздаётся на каждый (generic-член × MDO)
TypeSet.of(perMdoRef))); // ← intern + TypeSet тоже
}
}
mdoName, perMdoRef, BilingualString.of(mdoName), TypeSet.of(perMdoRef) зависят только от mdo, но вычисляются заново на каждый generic-член. Если generic-членов больше одного — это N-кратный дубляж обёрток BilingualString/TypeSet и лишние intern-ы на одни и те же значения.
buildPerOwnerCollectionMembers (per-owner, …Документы.X.Реквизиты.*)
Та же форма: childReturnType(...) (внутри — TypeSet.of/intern) вызывается на каждую пару (generic-член × child), хотя зависит только от child. (Имя там уже разделяется — ChildName хранит готовый BilingualString.)
Почему это «в логике», а не интернированием
Значения константны для ребёнка — их нужно посчитать один раз на ребёнка и переиспользовать на всех generic-членах. Это flyweight в коде: без интернер-мап и без CPU-оверхода в горячей фазе наполнения системы типов.
Предлагаемый фикс
Поднять вычисление per-child данных из цикла по generic-членам:
- предпосчитать на каждый
mdo/child пару (BilingualString name, TypeSet returnType) (и perMdoRef), затем переиспользовать во внутреннем цикле; либо поменять порядок циклов (внешний — по детям, внутренний — по generic-членам).
Масштаб
Эффект ограничен числом generic-членов базовой коллекции (множитель N). Память — порядка единиц МБ (это wrapper-дубляж поверх уже разделяемых строк/ref-ов, см. обсуждение в [соседнем профиле]), но фикс чистый, безопасный и убирает лишние intern-вызовы. Это «логический» близнец flyweight-промаха с именами коллекций (MetadataCollectionSpecializer.java:618, отмечен в #4191).
Проверка
MetadataCollectionSpecializerTest (12 тестов, gated BSL_LANGUAGE_SERVER_RUN_HBK_TESTS=true) — результат getMembers должен остаться побайтно тем же.
🤖 Generated with Claude Code
Суть
В
MetadataCollectionSpecializerматериализация членов коллекций устроена как двойной цикл «по generic-членам базовой коллекции × по детям», и данные ребёнка (имя, returnType) пересчитываются внутри цикла по generic-членам, хотя от конкретного члена они не зависят.materializeForMdos(группа коллекций,Метаданные.Документы.*)mdoName,perMdoRef,BilingualString.of(mdoName),TypeSet.of(perMdoRef)зависят только отmdo, но вычисляются заново на каждый generic-член. Если generic-членов больше одного — это N-кратный дубляж обёртокBilingualString/TypeSetи лишниеintern-ы на одни и те же значения.buildPerOwnerCollectionMembers(per-owner,…Документы.X.Реквизиты.*)Та же форма:
childReturnType(...)(внутри —TypeSet.of/intern) вызывается на каждую пару (generic-член × child), хотя зависит только отchild. (Имя там уже разделяется —ChildNameхранит готовыйBilingualString.)Почему это «в логике», а не интернированием
Значения константны для ребёнка — их нужно посчитать один раз на ребёнка и переиспользовать на всех generic-членах. Это flyweight в коде: без интернер-мап и без CPU-оверхода в горячей фазе наполнения системы типов.
Предлагаемый фикс
Поднять вычисление per-child данных из цикла по generic-членам:
mdo/childпару(BilingualString name, TypeSet returnType)(иperMdoRef), затем переиспользовать во внутреннем цикле; либо поменять порядок циклов (внешний — по детям, внутренний — по generic-членам).Масштаб
Эффект ограничен числом generic-членов базовой коллекции (множитель N). Память — порядка единиц МБ (это wrapper-дубляж поверх уже разделяемых строк/ref-ов, см. обсуждение в [соседнем профиле]), но фикс чистый, безопасный и убирает лишние
intern-вызовы. Это «логический» близнец flyweight-промаха с именами коллекций (MetadataCollectionSpecializer.java:618, отмечен в #4191).Проверка
MetadataCollectionSpecializerTest(12 тестов, gatedBSL_LANGUAGE_SERVER_RUN_HBK_TESTS=true) — результатgetMembersдолжен остаться побайтно тем же.🤖 Generated with Claude Code