1

Reflection is suppose to be a bit time consuming on android. so i was wondering given a function reference like this:

fun isOdd(x: Int) = x % 2 != 0
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd))

is ::isOdd call an unnecessary burden ?

would it be more efficient to not use them ?

UPDATE: doing some light metrics i did the following:

    class MainActivity : Activity() {

    val numbers = listOf(1, 2, 3)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        doRegular()
        doReflection()
    }

    fun isOdd(x: Int) = x % 2 != 0

    fun doRegular() {
        val start = System.currentTimeMillis()
        for (i in 0..999999) {
            numbers.filter { it % 2 != 0 }
        }
        Log.v("myTag", "regular: +${System.currentTimeMillis() - start}")
    }

    fun doReflection() {
        val start = System.currentTimeMillis()
        for (i in 0..999999) {
            numbers.filter(::isOdd)
        }
        Log.v("myTag", "reflection: ${System.currentTimeMillis() - start}")
    }
}

and the print statement results are:

//*****this was the first attempt before compiler could optimise anything
        regular: +577     
        reflection: 318
  //*********
  
    regular: +376
    reflection: 327
    
    regular: +593
     reflection: 343
    
     regular: +417
     reflection: 251
     
     regular: +243
     reflection: 238
     
     regular: +241
     reflection: 238
     
     regular: +238
     reflection: 237
     
     regular: +235
     reflection: 247
     
     regular: +237
     reflection: 247
     
     regular: +230
     reflection: 268

What would you conclude given these results?

update: some are asking why i think its using reflection. its based on this:

This stackoverflow answer seems to state its reflection: and the title for the official doc has reflection as the main heading: hence my confusion.

j2emanue
  • 60,549
  • 65
  • 286
  • 456
  • 1
    What makes you think `::isOdd` is using reflection? – Slaw Jul 25 '20 at 05:29
  • As a rule of thumb for benchmarks you run it like 5x or more, and discard big discrepancies. How many times did you run to get such numbers? Also important to know if jvm is fresh or already ran this code and had the chance to optimise it with some jit. – Fabio Jul 25 '20 at 05:42
  • @Slaw i commented in my question at the bottom, its based on two links – j2emanue Jul 25 '20 at 06:57
  • Maybe when Kotlin targets platforms other than the JVM it really does use reflection under the hood? Even when the function is _not_ inline I would expect, when the JVM is the target platform, for function references to work similar to method references in Java—which is not via reflection. – Slaw Jul 25 '20 at 08:58
  • Very interesting to see the last iterations converge to stable numbers. What might have happened here is that regular does some kind of optimisation. If on intellij try de compiling the bytecode into Java it may get you an answer. Also making isodd `inline` may make it even faster in some cases. Also compilers are too smart for some benchmarks to do what we think they do. – Fabio Jul 25 '20 at 11:59

3 Answers3

4

Surely ::isOdd is used for referencing the function, but unless it is really required to use the "reflection", it (reflection) is not used. Similar to how Int are changed into int in the bytecode unless we need to store the reference, if we create collections then only it is represented as java.lang.Integer.

The Kotlin compiler is smart and does the trick behind the hood.

/**
 * Returns a list containing only elements matching the given [predicate].
 * 
 * @sample samples.collections.Collections.Filtering.filter
 */
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

The filter function is inline function that embeds the lambda/reference directly at the call-site without actually using the reflection.

If you see the JVM bytecode, you'll see the following decompiled Java code:

// of numbers.filter { it % 2 != 0 }
while(var6.hasNext()) {
    Object element$iv$iv = var6.next();
    int it = ((Number)element$iv$iv).intValue();
    int var9 = false;
    if (it % 2 != 0) {              // <- check this out
        destination$iv$iv.add(element$iv$iv);
    }
}

// of numbers.filter(::isOdd)
while(var6.hasNext()) {
    Object element$iv$iv = var6.next();
    int p1 = ((Number)element$iv$iv).intValue();
    int var9 = false;
    if (isOdd(p1)) {                  // <- check this out
        destination$iv$iv.add(element$iv$iv);
    }
}

Realistically there is no reflection is involved here.

Sidenote: I've had a similar question over the official Kotlinlang slack at here, and I've got much attention but only 1 reply regarding it is only about metafacory as stated here in softwareengineering stackexchange subdomain.

Here's a ss: enter image description here

Animesh Sahu
  • 7,445
  • 2
  • 21
  • 49
2

Function references don't use reflection, they're like accessing a field, but for functions.

As such, these references are solved during compile time. If you made a mistake like misspelling the function name it won't compile. Where reflection for finding and calling the same misspelled function would fail at run time.

Fabio
  • 2,654
  • 16
  • 31
  • but this stackoverflow answer seems to state its reflection: https://stackoverflow.com/a/52463397/835883 and the title for the official doc has reflection as the main heading: https://kotlinlang.org/docs/reference/reflection.html hence my confusion. – j2emanue Jul 25 '20 at 05:59
  • That's a good point and I def need to read that more. My take is that your code treats it as lambda. And if you wish to treat it as reflection its the same syntax, but different use: `::odd.name` for example. Anyway, I need to peek at it with time to make sure I know what I'm talking. – Fabio Jul 25 '20 at 11:53
1

I would say that the best way to figure this out is to time it! Although, I would expect that Reflection would be more time consuming.

val numbers = listOf(1, 2, 3)

fun isOdd(x: Int) = x % 2 != 0

fun doRegular() {
    val start = System.currentTimeMillis()
    for (i in 0..999999) {
        numbers.filter { it % 2 != 0 }
    }
    println(System.currentTimeMillis() - start)
}

fun doReflection() {
    val start = System.currentTimeMillis()
    for (i in 0..999999) {
        numbers.filter(::isOdd)
    }
    println(System.currentTimeMillis() - start)
}
Dharman
  • 30,962
  • 25
  • 85
  • 135
daniel galarza
  • 289
  • 3
  • 5
  • There are [measureTimeMillis](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.system/measure-time-millis.html) and [measureNanoTIme](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.system/measure-nano-time.html) available in the standard library to measure that very percisely as well :^) – Animesh Sahu Jul 25 '20 at 12:01