Skip to content
This repository was archived by the owner on Jan 20, 2023. It is now read-only.

ProjectMapK/FastKFunction

Repository files navigation

License Lint, Test, upload Coveradge. codecov


FastKFunction

FastKFunction is a wrapper library for fast calls to KFunction.

Demo code

With just this description, you can call the KFunction faster.

data class Sample(
    val arg1: Int,
    val arg2: Int,
    val arg3: Int,
    val arg4: Int,
    val arg5: Int
)

val function: KFunction<Sample> = ::Sample

val fastKFunction: FastKFunction<Sample> = FastKFunction.of(function)

// call by vararg
val result: Sample = fastKFunction.call(1, 2, 3, 4, 5)

// call by Collection
val result: Sample = fastKFunction.callByCollection(listOf(1, 2, 3, 4, 5))

// call by ArgumentBucket
val result: Sample = fastKFunction.generateBucket()
        .apply { (0 until 5).forEach { this[it] = it + 1 }}
        .let { fastKFunction.callBy(it) }

How Fast?

Calling the constructor is more than 1.2 times faster than calling KFunction with call, and more than 6 times faster than calling it with callBy.

You can get the same speed as reflection in Java.

ops/s Ratio
Java Constructor 104267558.4 6.7
FastKFunction(call) 102948283.4 6.6
FastKFunction(callBy) 105609306.2 6.8
KFunction(call) 77096714.2 5.0
KFunction(callBy) 15519730.2 1

ConstructorBenchmarkResultGraph.png

*This is the score I got on a Ryzen7 3700X in a Windows 10 environment, with 3b8687 committed.

Raw data, and other comparisons

You can get full benchmark score and some other graphs here.

Mechanism

I have a blog post on the mechanism of fast invocation (in Japanese).

Benchmarking

You can run the benchmark with the ./gradlew jmh.
Please note that it will take about 5 hours in total if executed with the default settings.

./gradlew jmh

Installation

FastKFunction is published on JitPack. You can use this library on Maven, gradle and any other build tools. Please see here for the introduction method.

How to use FastKFunction.

Instance parameter.

If you call an instance function, you can expect a faster call with instance parameter.

data class Sample(
    val arg1: Int,
    val arg2: Int
) {
    fun instanceFun(arg3: Int): Int = arg1 + arg2 + arg3
}

val sample = Sample(1, 2)

val fastKFunction = FastKFunction.of(sample::instanceFun, sample)

Depending on how you get the KFunction, the instance parameter may be required. Even if the instance parameter is not required, passing an instance parameter will make the call faster.

How to call.

FastKFunction supports two major types of calls.

Call by vararg or Collection.

Calling with vararg or Collection is faster if you don't need to use the default arguments and can get them in the order in which they are defined.

val fastKFunction: FastKFunction<Sample> = FastKFunction.of(function)

// call by vararg
val result: Sample = fastKFunction.call(1, 2, 3, 4, 5)

// call by Collection
val result: Sample = fastKFunction.callByCollection(listOf(1, 2, 3, 4, 5))

Call by ArgumentBucket.

If the default argument is expected to be used, a call using ArgumentBucket is available.

ArgumentBucket has interfaces like MutableMap<KParameter, Any?>, which can be used, for example, as follows.

data class Sample(
    val arg1: Int,
    val arg2: Int = 0,
    val arg3: String? = null
)

val fastKFunction: FastKFunction<Sample> = FastKFunction.of(::Sample)

fun map(src: Map<String, Any?>): Sample {
    return fastKFunction.generateBucket()
        .apply { 
            fastKFunction.valueParameters.forEach {
                if (src.containsKey(it.name!!)) this[it] = src.getValue(it.name!!)
            }
        }.let { fastKFunction.callBy(it) }
}

For functions that can be called from a single argument.

For a function that can be called with a single argument, you can use the SingleArgFastKFunction.

data class Sample(val arg: Int)

val fastKFunction: SingleArgFastKFunction<Sample> = SingleArgFastKFunction.of(::Sample)

val result: Sample = fastKFunction.call(1)