Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

BotCommands module - Method accessors

This module provides abstractions to call user methods.

Installation

BotCommands-method-accessors-classfile on maven central

You can optionally install this dependency, it requires Java 24+ and can be enabled with MethodAccessorsConfig.preferClassFileAccessors().

Maven

<dependencies>
  <dependency>
    <groupId>io.github.freya022</groupId>
    <artifactId>BotCommands-method-accessors-classfile</artifactId>
    <version>VERSION</version>
  </dependency>
</dependencies>

Gradle

repositories {
    mavenCentral()
}

dependencies {
    implementation("io.github.freya022:BotCommands-method-accessors-classfile:VERSION")
}

Snapshots

To use the latest, unreleased changes, see SNAPSHOTS.md.

Implementations

kotlin-reflect

This is the default implementation, as is what the framework originally used, it only delegates to callSuspendBy with pairs of parameter -> value.

ClassFile-based

This newer implementation takes advantage of hidden classes and the Class-File API, which, for each function, generates a hidden class with instructions optimized to directly call the user method.

This allows for shorter stack traces in exceptions and the debugger, no InvocationTargetExceptions, and better performance.

Stack trace comparison

kotlin-reflect

Stack trace of kotlin-reflect call

ClassFile

Stack trace of custom caller

Performance comparison

Performance numbers from MethodAccessorBenchmark, baseline is a direct call:

Function type Baseline ClassFile kotlin-reflect
() -> String 0.114 µs/op
± 0,003
0.115 µs/op
± 0,003
0.244 µs/op
± 0,056
(a: String, b: Int = 42, c: Double = 3.14159) -> String 0.179 µs/op
± 0,007
0.184 µs/op
± 0,010
0.525 µs/op
± 0,039
suspend (a: String, b: Int = 42, c: Double = 3.14159) -> String 0.183 µs/op
± 0,006
0.179 µs/op
± 0,007
0.531 µs/op
± 0,030

Note that each benchmark only use one accessor instance, in the real world there would be many more accessors, meaning that each virtual call becomes non-trivial (see megamorphic virtual calls) and thus slower, therefore this benchmark only shows:

  • The custom classes can be as fast as direct calls
  • The overhead of kotlin-reflect

Performance in use-case

Due to the nature of the use case, the accessors will not be as fast as the numbers above may make it look like, but it won't be slow either.

For example, a slash command handler will need to call the appropriate user-defined method, and each method has its own accessor, meaning that, at the call site (where the method is called), the JVM has no clue which accessor it has to call!

Consequently, it will have to determine which implementation of the accessor it has to call (remember, 1 method = 1 implementation). Also note that this issue is due to the use case, a direct interface call, a custom accessor or a reflection call, would all have the same slowdown.