Skip to content

perf(types): не пересчитывать имя/returnType ребёнка в цикле по generic-членам (MetadataCollectionSpecializer) #4192

Description

@nixel2007

Суть

В 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions