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
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