FastEvent

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;