I would like to experiment with a compiler plugin that provides faster dynamic access to properties ...
r
I would like to experiment with a compiler plugin that provides faster dynamic access to properties of data classes than reflection, so read/write of named properties of an object. Can I leverage the serialization compiler plugin somehow or am I better off writing my own compiler plugin? This is for kotlin/jvm only
e
You can do
Copy code
Json.encodeToJsonElement(person).jsonObject["name"]
No clue if it's faster than reflection though.. I suppose it would encode the entire object..
e
reflection is pretty fast on JVM, is it actually a problem?
r
small jmh benchmark
Copy code
data class Pojo(val name: String, val age: Int) {
    private val nameGetter = this::class.declaredMemberProperties.first { it.name == "name" }.getter

    fun fetchDirectly(): String {
        return this.name
    }
    fun fetchViaGetter(): String {
        return nameGetter.call(this) as String
    }

}
Copy code
Benchmark                                       Mode  Cnt          Score          Error  Units
GetterAccessBenchmark.measureDirectAccess      thrpt   15  467950724,747 ± 11791341,287  ops/s
GetterAccessBenchmark.measureReflectionAccess  thrpt   15   25674364,892 ±   650107,316  ops/s
Copy code
import foo.Pojo;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

@Warmup(iterations = 2, time = 2, batchSize = 3)
@Measurement(iterations = 3, time = 2, batchSize = 4)
public class GetterAccessBenchmark {

    static Pojo pojo = new Pojo("Joe", 42);

    @Benchmark
    public void measureDirectAccess(Blackhole bh) {
        Object name = pojo.fetchDirectly();
        bh.consume(name);
    }

    @Benchmark
    public void measureReflectionAccess(Blackhole bh) {
        Object name = pojo.fetchViaGetter();
        bh.consume(name);
    }
}
but yes, in the larger context of my application there are of course other things than reflection that might be more worth-while optimizing but there is a lot of reflection access being done... Would be interesting to replace reflection in a smaller test with direct access
e
the measurements I get are much closer,
Copy code
Benchmark                                             Mode  Cnt         Score          Error  Units
GetterAccessBenchmark.measureDirectAccess            thrpt    3  56398569.746 ± 51533607.791  ops/s
GetterAccessBenchmark.measureReflectionAccess        thrpt    3  17567775.066 ± 26923892.904  ops/s
GetterAccessBenchmark.measureReflectionAccessCached  thrpt    3  18804598.799 ± 23139937.490  ops/s
where the cached version fetches the getter once, statically (for a slight improvement)
r
Mine was OpenJDK19 Corretto on M2 Mac
Using this code: https://github.com/graphql-java/graphql-java/blob/477d9a08965831b8a8f14245b428a793[…]rc/main/java/graphql/schema/fetching/LambdaFetchingSupport.java I get this (again on OpenJDK 19)
Copy code
Benchmark                                       Mode  Cnt          Score          Error  Units
GetterAccessBenchmark.measureDirectAccess      thrpt   15  446993080,524 ± 13086976,816  ops/s
GetterAccessBenchmark.measureHandleAccess      thrpt   15  333375173,469 ±  1198007,040  ops/s
GetterAccessBenchmark.measureReflectionAccess  thrpt   15   31950420,516 ±   349907,622  ops/s
and this
Copy code
package foo

import fetching.LambdaFetchingSupport
import kotlinx.serialization.Serializable
import kotlin.reflect.full.declaredMemberProperties

private val nameGetter = Pojo::class.declaredMemberProperties.first { it.name == "name" }.getter

private val nameHandle = LambdaFetchingSupport.createGetter(Pojo::class.java, "name")

@Serializable
data class Pojo(val name: String, val age: Int) {

    fun fetchDirectly(): String {
        return this.name
    }
    fun fetchViaGetter(): String {
        return nameGetter.call(this) as String
    }
    fun fetchViaHandle(): String {
        return nameHandle.get().apply(this) as String
    }

}
e
if you have MethodHandles available, you should be able to simply
MethodHandles.lookup().unreflect(kproperty.getter.javaMethod)
instead of going through that
r
ah thanks!
what jdk version was your benchmark on?
e
openjdk 17, linux x86_64
r
🤔
the difference is not that big in your benchmark, no
e
in any case. I don't think the serialization plugin will really help you. the generated serializer/descriptor can tell you what the available properties are, and you could write a custom serial format that only extracts the particular property or properties you want, but there's still a lot of unnecessary machinery involved and (probably) doesn't do what you want for more complex types
r
it seems to be targeting application startup time improvements
e
doesn't give properties afaik
you may as well write your own annotation processor that generates
Copy code
fun pojoGetter(name: String) = when (name) {
    "name" -> Pojo::name
    "age" -> Pojo::age
    else -> null
}
or whatever fits your use case
r
e
yes