Skip to content

Conversation

@PaintNinja
Copy link
Contributor

@PaintNinja PaintNinja commented May 18, 2025

This optimisation strips out some field reads and null checks for each listener at post-time when the event is final, not annotated with @Cancelable and not a subclass of GenericEvent, instead doing these checks at registration time.

To help reduce the risk of megamorphic calls negating this optimisation, the stripping does not apply for receiveCanceled listeners on @Cancelable events.

This is somewhat similar to EventBus 7's handling of cancellable vs non-cancellable events, but much more basic as it has no knowledge of the other listeners on the ListenerList and needs to maintain backwards-compatibility with existing behaviour (such as your listener not being called when the subclass of the event type you're listening to is cancellable but not the event type itself), among other things such as not being able to account for the sealed class hierarchy of events due to targeting Java 16.

Draft pending more comprehensive benchmarks

Benchmarks

Before

Benchmark                                                Mode  Cnt     Score    Error  Units
BenchmarkModLauncher.Posting.Dynamic.postDynamic         avgt    5     9.382 ±  0.400  ns/op
BenchmarkModLauncher.Posting.Dynamic.postDynamicDozen    avgt    5    98.981 ±  1.988  ns/op
BenchmarkModLauncher.Posting.Dynamic.postDynamicHundred  avgt    5   930.187 ± 15.463  ns/op
BenchmarkModLauncher.Posting.Lambda.postLambda           avgt    5     8.727 ±  0.020  ns/op
BenchmarkModLauncher.Posting.Lambda.postLambdaDozen      avgt    5    81.121 ±  0.169  ns/op
BenchmarkModLauncher.Posting.Lambda.postLambdaHundred    avgt    5   838.742 ± 39.586  ns/op
BenchmarkModLauncher.Posting.Mixed.postMixed             avgt    5     9.257 ±  0.021  ns/op
BenchmarkModLauncher.Posting.Mixed.postMixedDozen        avgt    5    83.753 ±  0.639  ns/op
BenchmarkModLauncher.Posting.Mixed.postMixedHundred      avgt    5   942.626 ± 31.968  ns/op
BenchmarkModLauncher.Posting.Static.postStatic           avgt    5     8.737 ±  0.008  ns/op
BenchmarkModLauncher.Posting.Static.postStaticDozen      avgt    5    79.215 ±  0.221  ns/op
BenchmarkModLauncher.Posting.Static.postStaticHundred    avgt    5  1046.316 ± 23.844  ns/op

Benchmark                                             Mode  Cnt     Score     Error  Units
BenchmarkNoLoader.Posting.Dynamic.postDynamic         avgt    5    13.926 ±   0.088  ns/op
BenchmarkNoLoader.Posting.Dynamic.postDynamicDozen    avgt    5    91.911 ±   3.094  ns/op
BenchmarkNoLoader.Posting.Dynamic.postDynamicHundred  avgt    5  1090.957 ±  21.577  ns/op
BenchmarkNoLoader.Posting.Lambda.postLambda           avgt    5    12.838 ±   0.035  ns/op
BenchmarkNoLoader.Posting.Lambda.postLambdaDozen      avgt    5    89.816 ±   0.119  ns/op
BenchmarkNoLoader.Posting.Lambda.postLambdaHundred    avgt    5  1134.081 ±  28.813  ns/op
BenchmarkNoLoader.Posting.Mixed.postMixed             avgt    5    13.441 ±   0.015  ns/op
BenchmarkNoLoader.Posting.Mixed.postMixedDozen        avgt    5    92.334 ±   2.404  ns/op
BenchmarkNoLoader.Posting.Mixed.postMixedHundred      avgt    5  1013.434 ±  45.331  ns/op
BenchmarkNoLoader.Posting.Static.postStatic           avgt    5    12.532 ±   0.034  ns/op
BenchmarkNoLoader.Posting.Static.postStaticDozen      avgt    5    88.695 ±   0.286  ns/op
BenchmarkNoLoader.Posting.Static.postStaticHundred    avgt    5  1067.041 ± 149.960  ns/op

After

Benchmark                                                Mode  Cnt     Score    Error  Units
BenchmarkModLauncher.Posting.Dynamic.postDynamic         avgt    5     6.114 ±  0.072  ns/op
BenchmarkModLauncher.Posting.Dynamic.postDynamicDozen    avgt    5    80.875 ±  1.615  ns/op
BenchmarkModLauncher.Posting.Dynamic.postDynamicHundred  avgt    5   926.775 ± 33.142  ns/op
BenchmarkModLauncher.Posting.Lambda.postLambda           avgt    5     5.350 ±  0.024  ns/op
BenchmarkModLauncher.Posting.Lambda.postLambdaDozen      avgt    5    78.216 ±  0.809  ns/op
BenchmarkModLauncher.Posting.Lambda.postLambdaHundred    avgt    5   805.043 ± 14.041  ns/op
BenchmarkModLauncher.Posting.Mixed.postMixed             avgt    5     6.115 ±  0.029  ns/op
BenchmarkModLauncher.Posting.Mixed.postMixedDozen        avgt    5    79.595 ±  0.198  ns/op
BenchmarkModLauncher.Posting.Mixed.postMixedHundred      avgt    5   916.002 ± 27.082  ns/op
BenchmarkModLauncher.Posting.Static.postStatic           avgt    5     5.429 ±  0.008  ns/op
BenchmarkModLauncher.Posting.Static.postStaticDozen      avgt    5    78.290 ±  1.566  ns/op
BenchmarkModLauncher.Posting.Static.postStaticHundred    avgt    5  1157.335 ± 38.852  ns/op

Benchmark                                             Mode  Cnt     Score    Error  Units
BenchmarkNoLoader.Posting.Dynamic.postDynamic         avgt    5    11.596 ±  2.023  ns/op
BenchmarkNoLoader.Posting.Dynamic.postDynamicDozen    avgt    5    89.962 ±  1.904  ns/op
BenchmarkNoLoader.Posting.Dynamic.postDynamicHundred  avgt    5   982.999 ± 35.802  ns/op
BenchmarkNoLoader.Posting.Lambda.postLambda           avgt    5    10.251 ±  0.013  ns/op
BenchmarkNoLoader.Posting.Lambda.postLambdaDozen      avgt    5    86.211 ±  0.435  ns/op
BenchmarkNoLoader.Posting.Lambda.postLambdaHundred    avgt    5   903.886 ± 48.524  ns/op
BenchmarkNoLoader.Posting.Mixed.postMixed             avgt    5    10.790 ±  0.329  ns/op
BenchmarkNoLoader.Posting.Mixed.postMixedDozen        avgt    5    90.486 ±  3.205  ns/op
BenchmarkNoLoader.Posting.Mixed.postMixedHundred      avgt    5  1079.462 ± 31.551  ns/op
BenchmarkNoLoader.Posting.Static.postStatic           avgt    5    10.434 ±  0.018  ns/op
BenchmarkNoLoader.Posting.Static.postStaticDozen      avgt    5    84.412 ±  0.217  ns/op
BenchmarkNoLoader.Posting.Static.postStaticHundred    avgt    5   961.416 ± 53.950  ns/op

@Jonathing Jonathing added the enhancement New feature or request label May 18, 2025
* the listener without additional redundant checks.</p>
*
* @implNote The 'all or nothing' nature of the post-time checks is to reduce the likelihood of megamorphic method
* invocation, which isn't as performant as monomorphic or bimorphic calls in Java 16
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole PR looks fine, but I did want to note that eventually i'd like to get a Java8 targeted EventBus branch buildable so these performance improvements can be backported to legacy Forge versions. Not sure if its possible for the 6.x branch or if we'd need a older version. Would need to look into exactly when and what was broken when the java version got bumped.

But its something to keep in mind.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Regarding Java 8 and legacy Forge, I'd like to discuss that later or closer to the date it happens and have Jonathan summarise it somewhere so it's easy to refer back to.

@PaintNinja PaintNinja marked this pull request as ready for review May 19, 2025 14:18
@PaintNinja
Copy link
Contributor Author

Updated the description with benchmark numbers, will merge shortly.

@PaintNinja PaintNinja merged commit e22d8c1 into MinecraftForge:6.2.x May 19, 2025
PaintNinja added a commit to PaintNinja/EventBus that referenced this pull request Oct 12, 2025
Enhances MinecraftForge#85 to support also checking the permitted subclasses when running on Java 17+ and the event type is sealed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants