Skip to content

Severe TPS loss from Insanity attachment — Beanification re-scanning on every construction (~49% of main thread time) #100

Description

@BinkoBanko

Mod version: Voidscape-1.21.1-1.9.588 (NeoForge 21.1.233, Minecraft 1.21.1)

Summary:
tamaized.voidscape.data.Insanity's constructor triggers a full Beanification annotation-injection pass (BeanContext.injectInto() -> runAnnotationProcessor() -> AutowiredAnnotationInjectBeanProcessor.process()) every time it's constructed, rather than caching the resolved bean definition once per class. On a live server this accounted for 48.9% of all main-thread tick time over a captured 60-minute window — consistently 40–53% of every single minute, not a burst.

Evidence (from a spark profile, decoded via protobuf):

  • ModDataAttachments.get() -> Insanity. -> BeanContext.injectInto(): 1,760,910 ms of the 3,601,960 ms total server-thread active time in the window (48.9%)

    • Breakdown of where that time goes inside the DI pass:

    • AutowiredAnnotationInjectBeanProcessor.process(): 1,248,290 ms

    • DirectoryAnnotationInjectBeanProcessor.process(): 510,460 ms

    • InternalReflectionHelper.classOrSuperEquals / getAllAutowiredFieldsIncludingSuper: several hundred thousand ms combined

    • Underlying cost is mostly ASM reflection comparisons - org.objectweb.asm.Type.equals alone: 174,420 ms self-time; String.charAt/checkIndex chains beneath it: 200,000+ ms self-time

  • By contrast, Insanity.tick() (the mob's actual per-tick logic) was only ~2,050 ms in the same window - confirming the cost is entirely construction/injection overhead, not gameplay logic.

  • Per-minute breakdown was flat (44–53% every minute for the first 42 of 61 windows), ruling out a one-time spike or GC artifact.

Trigger path: Entity.load() -> AttachmentHolder.deserializeAttachments() -> AttachmentType.read() -> ModDataAttachments.get() - i.e., this fires on entity data (de)serialization, so it scales with how often entities are loaded/saved, not player count.

Suggested fix: Cache the resolved AutowiredAnnotationInjectBeanProcessor bean definition per class (standard Spring-style containers resolve reflection metadata once and reuse it) instead of re-running getAllAutowiredFieldsIncludingSuper/ASM type comparison on every instantiation.

Spark Profile and Server Log Provided
rbVSqUNvt7.zip

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions