ManaRepair 刀架每 tick 事件 ReflectionHelper.getPrivateValue 反射取字段且异常被吞 #2

Closed
opened 2026-09-08 18:47:25 +00:00 by layue13 · 1 comment
Owner

问题描述

ManaRepair 监听主仓 OnEntityBladeStandUpdateEvent(每个刀架实体每 tick 触发):每次回调通过 ReflectionHelper.getPrivateValue(...) 反射读取事件对象私有字段 entityBladeStand(ManaRepair.java:20),无 Field 缓存;后续 Botania 缺失时的异常路径被空 catch 吞掉(:51-52),若处于该状态则每 tick 走完整反射+异常构造路径。玩家感知:多个加载中的刀架(自动化场景常见几十上百个)持续累积反射开销。

技术分析

代码位置:

  • 文件:src/main/java/net/langball/last_smith/addon/ManaRepair.java:17-52
  • 注册:CommonProxy.java:163(MinecraftForge.EVENT_BUS.register)

调用链:
刀架实体 onUpdate(每 tick/每刀架)
→ 主仓发布 OnEntityBladeStandUpdateEvent
→ ManaRepair.onUpdateBladeStand
→ ReflectionHelper.getPrivateValue(每次内部 findField 线性扫描类字段 + getAccessible)
→ 后续 Mana/TileEntity 查询

说明:反射字段查找(findField)每次对事件类做字符串匹配线性扫描,成本约为直接字段访问的百倍;主仓同类问题(实体反射构造)已修复,此处为独立出现点。

影响规模

每刀架每 tick 1 次反射查找。100 个刀架 = 2000 次 findField/秒。字段查找本身 O(字段数),叠加 Botania 缺失时每 tick 异常构造(填充栈轨迹,单次数十微秒级)更重。Tick:主线程累计;GC:异常对象与反射中间对象。

根因分析

重复计算:反射元数据(Field 对象)运行期恒定却每 tick 重新解析。生命周期:错误路径未做状态缓存。

优化方向

  1. private static final Field 缓存(static {} 中一次 findField + setAccessible);
  2. Botania 缺失状态在 init 期判定一次,用布尔短路替代每 tick 异常路径;
  3. 若事件类提供公共 getter 或事件本身在主仓已带字段(核对主仓 SlashBladeEvent 版本),直接改用公共访问。
    保持行为:修复逻辑与触发条件不变。不影响 Forge API/Mod API/存档/网络同步。

兼容性分析

反射目标为自有事件类,无跨模组 API 风险;Field 缓存需保持与原 getPrivateValue 相同的字段名解析结果。完全兼容。

验证方案

复现条件:放置 100 个刀架并持对应刀,Spark 采样 60 秒;
观察:ReflectionHelper.findField/getPrivateValue 调用栈占比,修改前后对比。

## 问题描述 ManaRepair 监听主仓 OnEntityBladeStandUpdateEvent(每个刀架实体每 tick 触发):每次回调通过 `ReflectionHelper.getPrivateValue(...)` 反射读取事件对象私有字段 entityBladeStand(ManaRepair.java:20),无 Field 缓存;后续 Botania 缺失时的异常路径被空 catch 吞掉(:51-52),若处于该状态则每 tick 走完整反射+异常构造路径。玩家感知:多个加载中的刀架(自动化场景常见几十上百个)持续累积反射开销。 ## 技术分析 代码位置: - 文件:src/main/java/net/langball/last_smith/addon/ManaRepair.java:17-52 - 注册:CommonProxy.java:163(MinecraftForge.EVENT_BUS.register) 调用链: 刀架实体 onUpdate(每 tick/每刀架) → 主仓发布 OnEntityBladeStandUpdateEvent → ManaRepair.onUpdateBladeStand → ReflectionHelper.getPrivateValue(每次内部 findField 线性扫描类字段 + getAccessible) → 后续 Mana/TileEntity 查询 说明:反射字段查找(findField)每次对事件类做字符串匹配线性扫描,成本约为直接字段访问的百倍;主仓同类问题(实体反射构造)已修复,此处为独立出现点。 ## 影响规模 每刀架每 tick 1 次反射查找。100 个刀架 = 2000 次 findField/秒。字段查找本身 O(字段数),叠加 Botania 缺失时每 tick 异常构造(填充栈轨迹,单次数十微秒级)更重。Tick:主线程累计;GC:异常对象与反射中间对象。 ## 根因分析 重复计算:反射元数据(Field 对象)运行期恒定却每 tick 重新解析。生命周期:错误路径未做状态缓存。 ## 优化方向 1. `private static final Field` 缓存(static {} 中一次 findField + setAccessible); 2. Botania 缺失状态在 init 期判定一次,用布尔短路替代每 tick 异常路径; 3. 若事件类提供公共 getter 或事件本身在主仓已带字段(核对主仓 SlashBladeEvent 版本),直接改用公共访问。 保持行为:修复逻辑与触发条件不变。不影响 Forge API/Mod API/存档/网络同步。 ## 兼容性分析 反射目标为自有事件类,无跨模组 API 风险;Field 缓存需保持与原 getPrivateValue 相同的字段名解析结果。完全兼容。 ## 验证方案 复现条件:放置 100 个刀架并持对应刀,Spark 采样 60 秒; 观察:ReflectionHelper.findField/getPrivateValue 调用栈占比,修改前后对比。
Author
Owner

Archive 组织仓已过时(Daniel 2026-09-23),整组织 issue 统一关闭。

Archive 组织仓已过时(Daniel 2026-09-23),整组织 issue 统一关闭。
layue13 added the
type
perf
priority
p2
labels 2026-10-06 21:07:42 +00:00
layue13 changed title from [性能][中] ManaRepair 刀架每 tick 事件 ReflectionHelper.getPrivateValue 反射取字段且异常被吞 to ManaRepair 刀架每 tick 事件 ReflectionHelper.getPrivateValue 反射取字段且异常被吞 2026-10-06 21:07:42 +00:00
This repo is archived. You cannot comment on issues.
No labels
priority
p2
type
perf
1 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Archive/slashblade-lastsmith#2