Проект закрыт
Коротко говоря, подход к оптимизации по-прежнему применим, но Forge/NeoForge не позволяют модифицировать EventBus.
В отличие от классов самого Minecraft или Forge, EventBus загружается не как часть игры, а как "библиотека", что делает невозможным его изменение через Mixin или Transformer.
В версии 1.2.0, в попытке дальнейшего повышения производительности, были использованы нестандартные методы для изменения класса ASMEventHandler
, которые, к сожалению, перестали работать в версиях 1.18.2+. Эти выпуски теперь заархивированы и, вероятно, не будут обновляться.
FastEvent
является модом-оптимизацией для Forge/Neoforge, улучшающим одну из фундаментальных систем в Forge: систему событий (Event system).
Насколько быстро
Создать тестовый сценарий для каждой поддерживаемой версии Minecraft сложно, особенно учитывая различия в системе событий между версиями. Поэтому ниже представлен отчет JMH-бенчмарка из PR, сделанного для проекта Cleanroom, где использовался тот же подход к оптимизации, что и в FastEvent:
Регистрация 10 000 слушателей событий, отправка события 0 раз:
Benchmark Mode Cnt Score Error Units
BusPerformanceTest.register10000Legacy avgt 5 1126.498 ± 284.633 ms/op
BusPerformanceTest.register10000Modern avgt 5 1058.961 ± 173.586 ms/op
Приблизительно на 6.4% быстрее.
Регистрация 1 000 слушателей событий, отправка события 10 000 раз:
Benchmark Mode Cnt Score Error Units
BusPerformanceTest.register1000test10000Legacy avgt 5 4407.963 ± 4250.643 ms/op
BusPerformanceTest.register1000test10000Modern avgt 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 во время выполнения генерирует классы-обработчики событий для каждого найденного метода, устраняя дорогостоящий вызов на основе отражения (reflection).
Но генерация классов также приводит к замедлению. Для дальнейшего ускорения FastEvent заменяет генерацию классов созданием лямбда-выражений, ускоряя построение обработчиков событий. Дополнительное преимущество заключается в том, что лямбда-выражения являются "скрытыми", позволяя 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 использует лямбда-выражение для создания обработчика события
IEventListener handler = lis::onEvent;