
FastEvent
加速Forge/NeoForge中的事件系统
项目已弃用
长话短说,优化方案仍然适用,但 Forge/NeoForge 不允许模组修改 EventBus。
与 Minecraft 或 Forge 自身的类不同,EventBus 并非作为游戏的一部分加载,而是作为“库”加载,这使得无法通过 Mixin 或 Transformer 修改 EventBus。
在 1.2.0 版本中,为了进一步提升性能,我使用了一些肮脏的技巧(如果你愿意,也可以称之为“非标准方法”)来修改 ASMEventHandler 类,但不幸的是这在 1.18.2+ 版本中失效了。这些版本已归档,可能不会再获得更新。
FastEvent
是一个 Forge/Neoforge 优化模组,它优化了 Forge 中最基础的系统之一:事件系统。
速度有多快
要为每个受支持的 Minecraft 版本制作测试用例并不容易,尤其是因为这些版本的事件系统各不相同。因此,我将提供一份我为 Cleanroom 项目提交的 PR 中的 JMH 基准测试报告,该 PR 使用了与 FastEvent 相同的优化方案:
注册 10,000 个事件监听器,不触发任何事件:
基准测试 模式 计数 分数 误差 单位
BusPerformanceTest.register10000Legacy 平均 5 1126.498 ± 284.633 ms/op
BusPerformanceTest.register10000Modern 平均 5 1058.961 ± 173.586 ms/op
大约快 6.4%。
注册 1,000 个事件监听器,触发事件 10,000 次:
基准测试 模式 计数 分数 误差 单位
BusPerformanceTest.register1000test10000Legacy 平均 5 4407.963 ± 4250.643 ms/op
BusPerformanceTest.register1000test10000Modern 平均 5 3550.578 ± 1991.352 ms/op
大约快 24%。
原始 PR
https://github.com/CleanroomMC/Cleanroom/pull/328#issuecomment-2801099504
它如何工作
(极客技术细节预警)
当开发者使用 @EventBusSubscriber 和/或 @SubscribeEvent 订阅事件处理器时,EventBus 无法神奇地获取事件处理器,而只能获得一个 Method 对象。因此最直接的方法是直接使用这个对象:method.invoke(...)。JVM 最终会将该调用重定向回原始方法,从而使事件处理器在订阅后能够接收到事件。
但这种方式(method.invoke(...))非常慢。为了加快速度,EventBus 会在运行时为找到的每个方法生成事件处理器类,从而消除昂贵的基于反射的调用。
但生成类又引入了新的性能下降。为了进一步提升速度,FastEvent 用 lambda 构造替代了类生成,加快了事件处理器的构造速度。另一个好处是 lambda 是“隐藏”的,允许 JVM 进行更多优化。
如果你恰好懂一点 Java,下面的代码示例可能会更直观:
class Listen {
public void onEvent(Event event) {
}
}
Listen lis = new Listen();
// EventBus 将为每个事件处理器生成一个新类
class IEventListener$Listen$onEvent implements IEventListener {
private Listen instance;
public IEventListener$Listen$onEvent(Listen instance) {
this.instance = instance;
}
@Override
public void invoke(Event event) {
instance.onEvent(event);
}
}
IEventListener handler = new IEventListener$Listen$onEvent(lis);
// FastEvent 使用 lambda 生成事件处理器
IEventListener handler = lis::onEvent;
正在加载版本记录…
正在加载评论…
评论在新手盒子客户端中发表,这里同步展示。